chore(): start moving some packages to the core directory (#7215)
This commit is contained in:
committed by
GitHub
parent
fdee748eed
commit
bbccd6481d
657
packages/cli/medusa-cli/src/commands/new.ts
Normal file
657
packages/cli/medusa-cli/src/commands/new.ts
Normal file
@@ -0,0 +1,657 @@
|
||||
/*
|
||||
* Adapted from https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-cli/src/init-starter.ts
|
||||
*/
|
||||
|
||||
import { execSync } from "child_process"
|
||||
import execa from "execa"
|
||||
import { sync as existsSync } from "fs-exists-cached"
|
||||
import fs from "fs-extra"
|
||||
import hostedGitInfo from "hosted-git-info"
|
||||
import isValid from "is-valid-path"
|
||||
import sysPath from "path"
|
||||
import prompts from "prompts"
|
||||
import { Pool } from "pg"
|
||||
import url from "url"
|
||||
import { createDatabase } from "pg-god"
|
||||
import { track } from "medusa-telemetry"
|
||||
import inquirer from "inquirer"
|
||||
|
||||
import reporter from "../reporter"
|
||||
import { getPackageManager, setPackageManager } from "../util/package-manager"
|
||||
import { PanicId } from "../reporter/panic-handler"
|
||||
import { clearProject } from "../util/clear-project"
|
||||
import path from "path"
|
||||
|
||||
const removeUndefined = (obj) => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj)
|
||||
.filter(([_, v]) => v != null)
|
||||
.map(([k, v]) => [k, v === Object(v) ? removeUndefined(v) : v])
|
||||
)
|
||||
}
|
||||
|
||||
const spawnWithArgs = (file, args, options) =>
|
||||
execa(file, args, { stdio: `inherit`, preferLocal: false, ...options })
|
||||
|
||||
const spawn = (cmd, options) => {
|
||||
const [file, ...args] = cmd.split(/\s+/)
|
||||
return spawnWithArgs(file, args, options)
|
||||
}
|
||||
// Checks the existence of yarn package
|
||||
// We use yarnpkg instead of yarn to avoid conflict with Hadoop yarn
|
||||
// Refer to https://github.com/yarnpkg/yarn/issues/673
|
||||
const checkForYarn = () => {
|
||||
try {
|
||||
execSync(`yarnpkg --version`, { stdio: `ignore` })
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const isAlreadyGitRepository = async () => {
|
||||
try {
|
||||
return await spawn(`git rev-parse --is-inside-work-tree`, {
|
||||
stdio: `pipe`,
|
||||
}).then((output) => output.stdout === `true`)
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize newly cloned directory as a git repo
|
||||
const gitInit = async (rootPath) => {
|
||||
reporter.info(`Initialising git in ${rootPath}`)
|
||||
|
||||
return await spawn(`git init`, { cwd: rootPath })
|
||||
}
|
||||
|
||||
// Create a .gitignore file if it is missing in the new directory
|
||||
const maybeCreateGitIgnore = async (rootPath) => {
|
||||
if (existsSync(sysPath.join(rootPath, `.gitignore`))) {
|
||||
return
|
||||
}
|
||||
|
||||
const gignore = reporter.activity(
|
||||
`Creating minimal .gitignore in ${rootPath}`
|
||||
)
|
||||
await fs.writeFile(
|
||||
sysPath.join(rootPath, `.gitignore`),
|
||||
`.cache\nnode_modules\npublic\n`
|
||||
)
|
||||
reporter.success(gignore, `Created .gitignore in ${rootPath}`)
|
||||
}
|
||||
|
||||
// Create an initial git commit in the new directory
|
||||
const createInitialGitCommit = async (rootPath, starterUrl) => {
|
||||
reporter.info(`Create initial git commit in ${rootPath}`)
|
||||
|
||||
await spawn(`git add -A`, { cwd: rootPath })
|
||||
// use execSync instead of spawn to handle git clients using
|
||||
// pgp signatures (with password)
|
||||
try {
|
||||
execSync(`git commit -m "Initial commit from medusa: (${starterUrl})"`, {
|
||||
cwd: rootPath,
|
||||
})
|
||||
} catch {
|
||||
// Remove git support if initial commit fails
|
||||
reporter.warn(`Initial git commit failed - removing git support\n`)
|
||||
fs.removeSync(sysPath.join(rootPath, `.git`))
|
||||
}
|
||||
}
|
||||
|
||||
// Executes `npm install` or `yarn install` in rootPath.
|
||||
const install = async (rootPath) => {
|
||||
const prevDir = process.cwd()
|
||||
|
||||
reporter.info(`Installing packages...`)
|
||||
console.log() // Add some space
|
||||
|
||||
process.chdir(rootPath)
|
||||
|
||||
const npmConfigUserAgent = process.env.npm_config_user_agent
|
||||
|
||||
try {
|
||||
if (!getPackageManager()) {
|
||||
if (npmConfigUserAgent?.includes(`yarn`)) {
|
||||
setPackageManager(`yarn`)
|
||||
} else {
|
||||
setPackageManager(`npm`)
|
||||
}
|
||||
}
|
||||
if (getPackageManager() === `yarn` && checkForYarn()) {
|
||||
await fs.remove(`package-lock.json`)
|
||||
await spawn(`yarnpkg`, {})
|
||||
} else {
|
||||
await fs.remove(`yarn.lock`)
|
||||
await spawn(`npm install`, {})
|
||||
}
|
||||
} finally {
|
||||
process.chdir(prevDir)
|
||||
}
|
||||
}
|
||||
|
||||
const ignored = (path) => !/^\.(git|hg)$/.test(sysPath.basename(path))
|
||||
|
||||
// Copy starter from file system.
|
||||
const copy = async (starterPath, rootPath) => {
|
||||
// Chmod with 755.
|
||||
// 493 = parseInt('755', 8)
|
||||
await fs.ensureDir(rootPath, { mode: 493 })
|
||||
|
||||
if (!existsSync(starterPath)) {
|
||||
throw new Error(`starter ${starterPath} doesn't exist`)
|
||||
}
|
||||
|
||||
if (starterPath === `.`) {
|
||||
throw new Error(
|
||||
`You can't create a starter from the existing directory. If you want to
|
||||
create a new project in the current directory, the trailing dot isn't
|
||||
necessary. If you want to create a project from a local starter, run
|
||||
something like "medusa new my-medusa-store ../local-medusa-starter"`
|
||||
)
|
||||
}
|
||||
|
||||
reporter.info(`Creating new site from local starter: ${starterPath}`)
|
||||
|
||||
const copyActivity = reporter.activity(
|
||||
`Copying local starter to ${rootPath} ...`
|
||||
)
|
||||
|
||||
await fs.copy(starterPath, rootPath, { filter: ignored })
|
||||
|
||||
reporter.success(copyActivity, `Created starter directory layout`)
|
||||
console.log() // Add some space
|
||||
|
||||
await install(rootPath)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Clones starter from URI.
|
||||
const clone = async (hostInfo, rootPath, v2 = false) => {
|
||||
let url
|
||||
// Let people use private repos accessed over SSH.
|
||||
if (hostInfo.getDefaultRepresentation() === `sshurl`) {
|
||||
url = hostInfo.ssh({ noCommittish: true })
|
||||
// Otherwise default to normal git syntax.
|
||||
} else {
|
||||
url = hostInfo.https({ noCommittish: true, noGitPlus: true })
|
||||
}
|
||||
|
||||
const branch = v2 ? [`-b`, "feat/v2"] :
|
||||
hostInfo.committish ? [`-b`, hostInfo.committish] : []
|
||||
|
||||
const createAct = reporter.activity(`Creating new project from git: ${url}`)
|
||||
|
||||
const args = [
|
||||
`clone`,
|
||||
...branch,
|
||||
url,
|
||||
rootPath,
|
||||
`--recursive`,
|
||||
`--depth=1`,
|
||||
].filter((arg) => Boolean(arg))
|
||||
|
||||
await execa(`git`, args, {})
|
||||
.then(() => {
|
||||
reporter.success(createAct, `Created starter directory layout`)
|
||||
})
|
||||
.catch((err) => {
|
||||
reporter.failure(createAct, `Failed to clone repository`)
|
||||
throw err
|
||||
})
|
||||
|
||||
await fs.remove(sysPath.join(rootPath, `.git`))
|
||||
|
||||
await install(rootPath)
|
||||
const isGit = await isAlreadyGitRepository()
|
||||
if (!isGit) await gitInit(rootPath)
|
||||
await maybeCreateGitIgnore(rootPath)
|
||||
if (!isGit) await createInitialGitCommit(rootPath, url)
|
||||
}
|
||||
|
||||
const getMedusaConfig = (rootPath) => {
|
||||
try {
|
||||
const configPath = sysPath.join(rootPath, "medusa-config.js")
|
||||
if (existsSync(configPath)) {
|
||||
const resolved = sysPath.resolve(configPath)
|
||||
const configModule = require(resolved)
|
||||
return configModule
|
||||
}
|
||||
throw Error()
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
reporter.warn(
|
||||
`Couldn't find a medusa-config.js file; please double check that you have the correct starter installed`
|
||||
)
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
const getPaths = async (starterPath, rootPath, v2 = false) => {
|
||||
let selectedOtherStarter = false
|
||||
|
||||
// if no args are passed, prompt user for path and starter
|
||||
if (!starterPath && !rootPath) {
|
||||
const response = await prompts.prompt([
|
||||
{
|
||||
type: `text`,
|
||||
name: `path`,
|
||||
message: `What is your project called?`,
|
||||
initial: `my-medusa-store`,
|
||||
},
|
||||
!v2 && {
|
||||
type: `select`,
|
||||
name: `starter`,
|
||||
message: `What starter would you like to use?`,
|
||||
choices: [
|
||||
{ title: `medusa-starter-default`, value: `medusa-starter-default` },
|
||||
{ title: `(Use a different starter)`, value: `different` },
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
])
|
||||
|
||||
// exit gracefully if responses aren't provided
|
||||
if ((!v2 && !response.starter) || !response.path.trim()) {
|
||||
throw new Error(
|
||||
`Please mention both starter package and project name along with path(if its not in the root)`
|
||||
)
|
||||
}
|
||||
|
||||
selectedOtherStarter = response.starter === `different`
|
||||
starterPath = `medusajs/${v2 ? "medusa-starter-default" : response.starter}`
|
||||
rootPath = response.path
|
||||
}
|
||||
|
||||
// set defaults if no root or starter has been set yet
|
||||
rootPath = rootPath || process.cwd()
|
||||
starterPath = starterPath || `medusajs/medusa-starter-default`
|
||||
|
||||
return { starterPath, rootPath, selectedOtherStarter }
|
||||
}
|
||||
|
||||
const successMessage = (path) => {
|
||||
reporter.info(`Your new Medusa project is ready for you! To start developing run:
|
||||
|
||||
cd ${path}
|
||||
medusa develop
|
||||
`)
|
||||
}
|
||||
|
||||
const defaultDBCreds = {
|
||||
user: process.env.USER || "postgres",
|
||||
database: "postgres",
|
||||
password: "",
|
||||
port: 5432,
|
||||
host: "localhost",
|
||||
}
|
||||
|
||||
const verifyPgCreds = async (creds) => {
|
||||
const pool = new Pool(creds)
|
||||
return new Promise((resolve, reject) => {
|
||||
pool.query("SELECT NOW()", (err, res) => {
|
||||
pool.end()
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(res)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const interactiveDbCreds = async (dbName, dbCreds = {}) => {
|
||||
const credentials = Object.assign({}, defaultDBCreds, dbCreds)
|
||||
|
||||
const collecting = true
|
||||
|
||||
while (collecting) {
|
||||
const result = await inquirer
|
||||
.prompt([
|
||||
{
|
||||
type: "list",
|
||||
name: "continueWithDefault",
|
||||
message: `
|
||||
|
||||
Will attempt to setup Postgres database "${dbName}" with credentials:
|
||||
user: ${credentials.user}
|
||||
password: ***
|
||||
port: ${credentials.port}
|
||||
host: ${credentials.host}
|
||||
Do you wish to continue with these credentials?
|
||||
|
||||
`,
|
||||
choices: [`Continue`, `Change credentials`, `Skip database setup`],
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
when: ({ continueWithDefault }) =>
|
||||
continueWithDefault === `Change credentials`,
|
||||
name: "user",
|
||||
default: credentials.user,
|
||||
message: `DB user`,
|
||||
},
|
||||
{
|
||||
type: "password",
|
||||
when: ({ continueWithDefault }) =>
|
||||
continueWithDefault === `Change credentials`,
|
||||
name: "password",
|
||||
default: credentials.password,
|
||||
message: `DB password`,
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
when: ({ continueWithDefault }) =>
|
||||
continueWithDefault === `Change credentials`,
|
||||
name: "port",
|
||||
default: credentials.port,
|
||||
message: `DB port`,
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
when: ({ continueWithDefault }) =>
|
||||
continueWithDefault === `Change credentials`,
|
||||
name: "host",
|
||||
default: credentials.host,
|
||||
message: `DB host`,
|
||||
},
|
||||
])
|
||||
.then(async (answers) => {
|
||||
const collectedCreds = Object.assign({}, credentials, {
|
||||
user: answers.user,
|
||||
password: answers.password,
|
||||
host: answers.host,
|
||||
port: answers.port,
|
||||
})
|
||||
|
||||
switch (answers.continueWithDefault) {
|
||||
case "Continue": {
|
||||
const done = await verifyPgCreds(credentials).catch((_) => false)
|
||||
if (done) {
|
||||
return credentials
|
||||
}
|
||||
return false
|
||||
}
|
||||
case "Change credentials": {
|
||||
const done = await verifyPgCreds(collectedCreds).catch((_) => false)
|
||||
if (done) {
|
||||
return collectedCreds
|
||||
}
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
if (result !== false) {
|
||||
return result
|
||||
}
|
||||
|
||||
console.log("\n\nCould not verify DB credentials - please try again\n\n")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const setupDB = async (dbName, dbCreds = {}) => {
|
||||
const credentials = Object.assign({}, defaultDBCreds, dbCreds)
|
||||
|
||||
const dbActivity = reporter.activity(`Setting up database "${dbName}"...`)
|
||||
await createDatabase(
|
||||
{
|
||||
databaseName: dbName,
|
||||
errorIfExist: true,
|
||||
},
|
||||
credentials
|
||||
)
|
||||
.then(() => {
|
||||
reporter.success(dbActivity, `Created database "${dbName}"`)
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.name === "PDG_ERR::DuplicateDatabase") {
|
||||
reporter.success(
|
||||
dbActivity,
|
||||
`Database ${dbName} already exists; skipping setup`
|
||||
)
|
||||
} else {
|
||||
reporter.failure(dbActivity, `Skipping database setup.`)
|
||||
reporter.warn(
|
||||
`Failed to setup database; install PostgresQL or make sure to manage your database connection manually`
|
||||
)
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const setupEnvVars = async (rootPath, dbName, dbCreds = {}) => {
|
||||
const templatePath = sysPath.join(rootPath, ".env.template")
|
||||
const destination = sysPath.join(rootPath, ".env")
|
||||
if (existsSync(templatePath)) {
|
||||
fs.renameSync(templatePath, destination)
|
||||
}
|
||||
|
||||
const credentials = Object.assign({}, defaultDBCreds, dbCreds)
|
||||
let dbUrl = ""
|
||||
if (
|
||||
credentials.user !== defaultDBCreds.user ||
|
||||
credentials.password !== defaultDBCreds.password
|
||||
) {
|
||||
dbUrl = `postgres://${credentials.user}:${credentials.password}@${credentials.host}:${credentials.port}/${dbName}`
|
||||
} else {
|
||||
dbUrl = `postgres://${credentials.host}:${credentials.port}/${dbName}`
|
||||
}
|
||||
|
||||
fs.appendFileSync(destination, `DATABASE_URL=${dbUrl}\n`)
|
||||
}
|
||||
|
||||
const runMigrations = async (rootPath) => {
|
||||
const migrationActivity = reporter.activity("Applying database migrations...")
|
||||
|
||||
const cliPath = sysPath.join(
|
||||
`node_modules`,
|
||||
`@medusajs`,
|
||||
`medusa-cli`,
|
||||
`cli.js`
|
||||
)
|
||||
|
||||
return await execa(cliPath, [`migrations`, `run`], {
|
||||
cwd: rootPath,
|
||||
})
|
||||
.then(() => {
|
||||
reporter.success(migrationActivity, "Database migrations completed.")
|
||||
})
|
||||
.catch((err) => {
|
||||
reporter.failure(
|
||||
migrationActivity,
|
||||
"Failed to migrate database you must complete migration manually before starting your server."
|
||||
)
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
const attemptSeed = async (rootPath) => {
|
||||
const seedActivity = reporter.activity("Seeding database")
|
||||
|
||||
const pkgPath = sysPath.resolve(rootPath, "package.json")
|
||||
if (existsSync(pkgPath)) {
|
||||
const pkg = require(pkgPath)
|
||||
if (pkg.scripts && pkg.scripts.seed) {
|
||||
const proc = execa(getPackageManager(), [`run`, `seed`], {
|
||||
cwd: rootPath,
|
||||
})
|
||||
|
||||
// Useful for development
|
||||
// proc.stdout.pipe(process.stdout)
|
||||
|
||||
await proc
|
||||
.then(() => {
|
||||
reporter.success(seedActivity, "Seed completed")
|
||||
})
|
||||
.catch((err) => {
|
||||
reporter.failure(seedActivity, "Failed to complete seed; skipping")
|
||||
console.error(err)
|
||||
})
|
||||
} else {
|
||||
reporter.failure(
|
||||
seedActivity,
|
||||
"Starter doesn't provide a seed command; skipping."
|
||||
)
|
||||
}
|
||||
} else {
|
||||
reporter.failure(seedActivity, "Could not find package.json")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function that clones or copies the starter.
|
||||
*/
|
||||
export const newStarter = async (args) => {
|
||||
track("CLI_NEW")
|
||||
|
||||
const {
|
||||
starter,
|
||||
root,
|
||||
skipDb,
|
||||
skipMigrations,
|
||||
skipEnv,
|
||||
seed,
|
||||
useDefaults,
|
||||
dbUser,
|
||||
dbDatabase,
|
||||
dbPass,
|
||||
dbPort,
|
||||
dbHost,
|
||||
v2
|
||||
} = args
|
||||
|
||||
const dbCredentials = removeUndefined({
|
||||
user: dbUser,
|
||||
database: dbDatabase,
|
||||
password: dbPass,
|
||||
port: dbPort,
|
||||
host: dbHost,
|
||||
})
|
||||
|
||||
const { starterPath, rootPath, selectedOtherStarter } = await getPaths(
|
||||
starter,
|
||||
root,
|
||||
v2
|
||||
)
|
||||
|
||||
const urlObject = url.parse(rootPath)
|
||||
|
||||
if (selectedOtherStarter) {
|
||||
reporter.info(
|
||||
`Find the url of the Medusa starter you wish to create and run:
|
||||
|
||||
medusa new ${rootPath} [url-to-starter]
|
||||
|
||||
`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (urlObject.protocol && urlObject.host) {
|
||||
const isStarterAUrl =
|
||||
starter && !url.parse(starter).hostname && !url.parse(starter).protocol
|
||||
|
||||
if (/medusa-starter/gi.test(rootPath) && isStarterAUrl) {
|
||||
reporter.panic({
|
||||
id: PanicId.InvalidProjectName,
|
||||
context: {
|
||||
starter,
|
||||
rootPath,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
reporter.panic({
|
||||
id: PanicId.InvalidProjectName,
|
||||
context: {
|
||||
rootPath,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!isValid(rootPath)) {
|
||||
reporter.panic({
|
||||
id: PanicId.InvalidPath,
|
||||
context: {
|
||||
path: sysPath.resolve(rootPath),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (existsSync(sysPath.join(rootPath, `package.json`))) {
|
||||
reporter.panic({
|
||||
id: PanicId.AlreadyNodeProject,
|
||||
context: {
|
||||
rootPath,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const hostedInfo = hostedGitInfo.fromUrl(starterPath)
|
||||
if (hostedInfo) {
|
||||
await clone(hostedInfo, rootPath, v2)
|
||||
} else {
|
||||
await copy(starterPath, rootPath)
|
||||
}
|
||||
|
||||
track("CLI_NEW_LAYOUT_COMPLETED")
|
||||
|
||||
let creds = dbCredentials
|
||||
|
||||
const dbName = `medusa-db-${Math.random().toString(36).substring(2, 7)}` // generate random 5 character string
|
||||
|
||||
if (!useDefaults && !skipDb && !skipEnv) {
|
||||
creds = await interactiveDbCreds(dbName, dbCredentials)
|
||||
}
|
||||
|
||||
if (creds === null) {
|
||||
reporter.info(
|
||||
"Skipping automatic database setup. Please note that you need to create a database and run migrations before you can run your Medusa backend"
|
||||
)
|
||||
} else {
|
||||
if (!skipDb) {
|
||||
track("CLI_NEW_SETUP_DB")
|
||||
await setupDB(dbName, creds)
|
||||
}
|
||||
|
||||
if (!skipEnv) {
|
||||
track("CLI_NEW_SETUP_ENV")
|
||||
await setupEnvVars(rootPath, dbName, creds)
|
||||
}
|
||||
|
||||
if (!skipMigrations) {
|
||||
track("CLI_NEW_RUN_MIGRATIONS")
|
||||
await runMigrations(rootPath)
|
||||
}
|
||||
|
||||
if (seed) {
|
||||
track("CLI_NEW_SEED_DB")
|
||||
await attemptSeed(rootPath)
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectedOtherStarter) {
|
||||
reporter.info("Final project preparations...")
|
||||
// remove demo files
|
||||
clearProject(rootPath)
|
||||
// remove .git directory
|
||||
fs.rmSync(path.join(rootPath, '.git'), {
|
||||
recursive: true,
|
||||
force: true,
|
||||
})
|
||||
}
|
||||
|
||||
successMessage(rootPath)
|
||||
track("CLI_NEW_SUCCEEDED")
|
||||
}
|
||||
415
packages/cli/medusa-cli/src/create-cli.ts
Normal file
415
packages/cli/medusa-cli/src/create-cli.ts
Normal file
@@ -0,0 +1,415 @@
|
||||
import { sync as existsSync } from "fs-exists-cached"
|
||||
import { setTelemetryEnabled } from "medusa-telemetry"
|
||||
import path from "path"
|
||||
import resolveCwd from "resolve-cwd"
|
||||
|
||||
import { didYouMean } from "./did-you-mean"
|
||||
import { getLocalMedusaVersion } from "./util/version"
|
||||
|
||||
import { newStarter } from "./commands/new"
|
||||
import reporter from "./reporter"
|
||||
|
||||
const yargs = require(`yargs`)
|
||||
|
||||
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) {
|
||||
projectInfo["sitePackageJson"] = require(path.join(
|
||||
directory,
|
||||
`package.json`
|
||||
))
|
||||
}
|
||||
|
||||
function resolveLocalCommand(command) {
|
||||
if (!isLocalProject) {
|
||||
cli.showHelp()
|
||||
}
|
||||
|
||||
try {
|
||||
const cmdPath = resolveCwd.silent(
|
||||
`@medusajs/medusa/dist/commands/${command}`
|
||||
)!
|
||||
return require(cmdPath).default
|
||||
} catch (err) {
|
||||
if (!process.env.NODE_ENV?.startsWith("prod")) {
|
||||
console.log("--------------- ERROR ---------------------")
|
||||
console.log(err)
|
||||
console.log("-------------------------------------------")
|
||||
}
|
||||
cli.showHelp()
|
||||
}
|
||||
}
|
||||
|
||||
function getCommandHandler(command, handler) {
|
||||
return (argv) => {
|
||||
const localCmd = resolveLocalCommand(command)
|
||||
const args = { ...argv, ...projectInfo, useYarn }
|
||||
|
||||
return handler ? handler(args, localCmd) : localCmd(args)
|
||||
}
|
||||
}
|
||||
|
||||
cli
|
||||
.command({
|
||||
command: `new [root] [starter]`,
|
||||
builder: (_) =>
|
||||
_.option(`seed`, {
|
||||
type: `boolean`,
|
||||
describe: `If flag is set the command will attempt to seed the database after setup.`,
|
||||
default: false,
|
||||
})
|
||||
.option(`y`, {
|
||||
type: `boolean`,
|
||||
alias: "useDefaults",
|
||||
describe: `If flag is set the command will not interactively collect database credentials`,
|
||||
default: false,
|
||||
})
|
||||
.option(`skip-db`, {
|
||||
type: `boolean`,
|
||||
describe: `If flag is set the command will not attempt to complete database setup`,
|
||||
default: false,
|
||||
})
|
||||
.option(`skip-migrations`, {
|
||||
type: `boolean`,
|
||||
describe: `If flag is set the command will not attempt to complete database migration`,
|
||||
default: false,
|
||||
})
|
||||
.option(`skip-env`, {
|
||||
type: `boolean`,
|
||||
describe: `If flag is set the command will not attempt to populate .env`,
|
||||
default: false,
|
||||
})
|
||||
.option(`db-user`, {
|
||||
type: `string`,
|
||||
describe: `The database user to use for database setup and migrations.`,
|
||||
})
|
||||
.option(`db-database`, {
|
||||
type: `string`,
|
||||
describe: `The database use for database setup and migrations.`,
|
||||
})
|
||||
.option(`db-pass`, {
|
||||
type: `string`,
|
||||
describe: `The database password to use for database setup and migrations.`,
|
||||
})
|
||||
.option(`db-port`, {
|
||||
type: `number`,
|
||||
describe: `The database port to use for database setup and migrations.`,
|
||||
})
|
||||
.option(`db-host`, {
|
||||
type: `string`,
|
||||
describe: `The database host to use for database setup and migrations.`,
|
||||
})
|
||||
.option(`v2`, {
|
||||
type: `boolean`,
|
||||
describe: `Install Medusa with the V2 feature flag enabled. WARNING: Medusa V2 is still in development and shouldn't be used in production.`,
|
||||
default: false
|
||||
}),
|
||||
desc: `Create a new Medusa project.`,
|
||||
handler: handlerP(newStarter),
|
||||
})
|
||||
.command({
|
||||
command: `telemetry`,
|
||||
describe: `Enable or disable collection of anonymous usage data.`,
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.option(`enable`, {
|
||||
type: `boolean`,
|
||||
description: `Enable telemetry (default)`,
|
||||
})
|
||||
.option(`disable`, {
|
||||
type: `boolean`,
|
||||
description: `Disable telemetry`,
|
||||
}),
|
||||
|
||||
handler: handlerP(({ enable, disable }) => {
|
||||
const enabled = Boolean(enable) || !disable
|
||||
setTelemetryEnabled(enabled)
|
||||
reporter.info(
|
||||
`Telemetry collection ${enabled ? `enabled` : `disabled`}`
|
||||
)
|
||||
}),
|
||||
})
|
||||
.command({
|
||||
command: `seed`,
|
||||
desc: `Migrates and populates the database with the provided file.`,
|
||||
builder: (_) =>
|
||||
_.option(`f`, {
|
||||
alias: `seed-file`,
|
||||
type: `string`,
|
||||
describe: `Path to the file where the seed is defined.`,
|
||||
required: true,
|
||||
}).option(`m`, {
|
||||
alias: `migrate`,
|
||||
type: `boolean`,
|
||||
default: true,
|
||||
describe: `Flag to indicate if migrations should be run prior to seeding the database`,
|
||||
}),
|
||||
handler: handlerP(
|
||||
getCommandHandler(`seed`, (args, cmd) => {
|
||||
process.env.NODE_ENV ??= `development`
|
||||
return cmd(args)
|
||||
})
|
||||
),
|
||||
})
|
||||
.command({
|
||||
command: `migrations [action]`,
|
||||
desc: `Manage migrations from the core and your own project`,
|
||||
builder: {
|
||||
action: {
|
||||
demand: true,
|
||||
choices: ["run", "revert", "show"],
|
||||
},
|
||||
},
|
||||
handler: handlerP(
|
||||
getCommandHandler(`migrate`, (args, cmd) => {
|
||||
process.env.NODE_ENV = process.env.NODE_ENV || `development`
|
||||
return cmd(args)
|
||||
})
|
||||
),
|
||||
})
|
||||
.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: `start-cluster`,
|
||||
desc: `Start development server in cluster mode (beta).`,
|
||||
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}`,
|
||||
})
|
||||
.option(`c`, {
|
||||
alias: `cpus`,
|
||||
type: `number`,
|
||||
default: process.env.CPUS,
|
||||
describe:
|
||||
"Set number of cpus to use. Defaults to max number of cpus available on the system (set by env.CPUS)",
|
||||
}),
|
||||
handler: handlerP(
|
||||
getCommandHandler(`start-cluster`, (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.`,
|
||||
})
|
||||
.option(`i`, {
|
||||
alias: `id`,
|
||||
type: `string`,
|
||||
describe: `User's id.`,
|
||||
})
|
||||
.option(`invite`, {
|
||||
type: `boolean`,
|
||||
describe: `If flag is set, an invitation will be created instead of a new user and the invite token will be returned.`,
|
||||
default: false,
|
||||
}),
|
||||
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}`
|
||||
}
|
||||
}
|
||||
|
||||
export default (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) : ``
|
||||
|
||||
if (!process.env.NODE_ENV?.startsWith("prod")) {
|
||||
console.log("--------------- ERROR ---------------------")
|
||||
console.log(err)
|
||||
console.log("-------------------------------------------")
|
||||
}
|
||||
|
||||
cli.showHelp()
|
||||
reporter.info(suggestion)
|
||||
reporter.info(msg)
|
||||
})
|
||||
.parse(argv.slice(2))
|
||||
}
|
||||
18
packages/cli/medusa-cli/src/did-you-mean.ts
Normal file
18
packages/cli/medusa-cli/src/did-you-mean.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import meant from "meant"
|
||||
|
||||
export function didYouMean(scmd, commands): string {
|
||||
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`
|
||||
)
|
||||
}
|
||||
}
|
||||
44
packages/cli/medusa-cli/src/index.ts
Normal file
44
packages/cli/medusa-cli/src/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import "core-js/stable"
|
||||
import "regenerator-runtime/runtime"
|
||||
import os from "os"
|
||||
import util from "util"
|
||||
import createCli from "./create-cli"
|
||||
|
||||
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`
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,24 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Reporter handles "Error" signature correctly 1`] = `
|
||||
Object {
|
||||
"level": "error",
|
||||
"message": "Error",
|
||||
"stack": Any<Array>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Reporter handles "String" signature correctly 1`] = `
|
||||
Object {
|
||||
"level": "error",
|
||||
"message": "Test log",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Reporter handles "String, Error" signature correctly 1`] = `
|
||||
Object {
|
||||
"level": "error",
|
||||
"message": "Test log",
|
||||
"stack": Any<Array>,
|
||||
}
|
||||
`;
|
||||
49
packages/cli/medusa-cli/src/reporter/__tests__/index.js
Normal file
49
packages/cli/medusa-cli/src/reporter/__tests__/index.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import logger, { Reporter } from "../"
|
||||
|
||||
describe(`Reporter`, () => {
|
||||
const winstonMock = {
|
||||
log: jest.fn(),
|
||||
}
|
||||
|
||||
const reporter = new Reporter({
|
||||
logger: winstonMock,
|
||||
activityLogger: {},
|
||||
})
|
||||
|
||||
const getErrorMessages = fn =>
|
||||
fn.mock.calls
|
||||
.map(([firstArg]) => firstArg)
|
||||
.filter(structuredMessage => structuredMessage.level === `error`)
|
||||
|
||||
beforeEach(() => {
|
||||
winstonMock.log.mockClear()
|
||||
})
|
||||
|
||||
it(`handles "String" signature correctly`, () => {
|
||||
reporter.error("Test log")
|
||||
|
||||
const generated = getErrorMessages(winstonMock.log)[0]
|
||||
|
||||
expect(generated).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it(`handles "String, Error" signature correctly`, () => {
|
||||
reporter.error("Test log", new Error("String Error"))
|
||||
|
||||
const generated = getErrorMessages(winstonMock.log)[0]
|
||||
|
||||
expect(generated).toMatchSnapshot({
|
||||
stack: expect.any(Array),
|
||||
})
|
||||
})
|
||||
|
||||
it(`handles "Error" signature correctly`, () => {
|
||||
reporter.error(new Error("Error"))
|
||||
|
||||
const generated = getErrorMessages(winstonMock.log)[0]
|
||||
|
||||
expect(generated).toMatchSnapshot({
|
||||
stack: expect.any(Array),
|
||||
})
|
||||
})
|
||||
})
|
||||
332
packages/cli/medusa-cli/src/reporter/index.ts
Normal file
332
packages/cli/medusa-cli/src/reporter/index.ts
Normal file
@@ -0,0 +1,332 @@
|
||||
import stackTrace from "stack-trace"
|
||||
import { ulid } from "ulid"
|
||||
import winston from "winston"
|
||||
import ora from "ora"
|
||||
import { track } from "medusa-telemetry"
|
||||
|
||||
import { panicHandler } from "./panic-handler"
|
||||
import * as Transport from "winston-transport"
|
||||
|
||||
const LOG_LEVEL = process.env.LOG_LEVEL || "silly"
|
||||
const LOG_FILE = process.env.LOG_FILE || ""
|
||||
const NODE_ENV = process.env.NODE_ENV || "development"
|
||||
const IS_DEV = NODE_ENV.startsWith("dev")
|
||||
|
||||
let transports: Transport[] = []
|
||||
|
||||
if (!IS_DEV) {
|
||||
transports.push(new winston.transports.Console())
|
||||
} else {
|
||||
transports.push(
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.cli(),
|
||||
winston.format.splat()
|
||||
),
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (LOG_FILE) {
|
||||
transports.push(
|
||||
new winston.transports.File({
|
||||
filename: LOG_FILE
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const loggerInstance = winston.createLogger({
|
||||
level: LOG_LEVEL,
|
||||
levels: winston.config.npm.levels,
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({
|
||||
format: "YYYY-MM-DD HH:mm:ss",
|
||||
}),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.splat(),
|
||||
winston.format.json()
|
||||
),
|
||||
transports,
|
||||
})
|
||||
|
||||
export class Reporter {
|
||||
protected activities_: Record<string, any>
|
||||
protected loggerInstance_: winston.Logger
|
||||
protected ora_: typeof ora
|
||||
|
||||
constructor({ logger, activityLogger }) {
|
||||
this.activities_ = {}
|
||||
this.loggerInstance_ = logger
|
||||
this.ora_ = activityLogger
|
||||
}
|
||||
|
||||
panic = (data) => {
|
||||
const parsedPanic = panicHandler(data)
|
||||
|
||||
this.loggerInstance_.log({
|
||||
level: "error",
|
||||
details: data,
|
||||
message: parsedPanic.message,
|
||||
})
|
||||
|
||||
track("PANIC_ERROR_REACHED", {
|
||||
id: data.id,
|
||||
})
|
||||
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the logger should log at a given level.
|
||||
* @param {string} level - the level to check if logger is configured for
|
||||
* @return {boolean} whether we should log
|
||||
*/
|
||||
shouldLog = (level) => {
|
||||
level = this.loggerInstance_.levels[level]
|
||||
const logLevel = this.loggerInstance_.levels[this.loggerInstance_.level]
|
||||
return level <= logLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the log level of the logger.
|
||||
* @param {string} level - the level to set the logger to
|
||||
*/
|
||||
setLogLevel = (level) => {
|
||||
this.loggerInstance_.level = level
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the logger to the value specified by the LOG_LEVEL env var. If no
|
||||
* LOG_LEVEL is set it defaults to "silly".
|
||||
*/
|
||||
unsetLogLevel = () => {
|
||||
this.loggerInstance_.level = LOG_LEVEL
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin an activity. In development an activity is displayed as a spinner;
|
||||
* in other environments it will log the activity at the info level.
|
||||
* @param {string} message - the message to log the activity under
|
||||
* @param {any} config
|
||||
* @returns {string} the id of the activity; this should be passed to do
|
||||
* further operations on the activity such as success, failure, progress.
|
||||
*/
|
||||
activity = (message, config = {}) => {
|
||||
const id = ulid()
|
||||
if (IS_DEV && this.shouldLog("info")) {
|
||||
const activity = this.ora_(message).start()
|
||||
|
||||
this.activities_[id] = {
|
||||
activity,
|
||||
config,
|
||||
start: Date.now(),
|
||||
}
|
||||
|
||||
return id
|
||||
} else {
|
||||
this.activities_[id] = {
|
||||
start: Date.now(),
|
||||
config,
|
||||
}
|
||||
this.loggerInstance_.log({
|
||||
activity_id: id,
|
||||
level: "info",
|
||||
config,
|
||||
message,
|
||||
})
|
||||
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports progress on an activity. In development this will update the
|
||||
* activity log message, in other environments a log message will be issued
|
||||
* at the info level. Logging will include the activityId.
|
||||
* @param {string} activityId - the id of the activity as returned by activity
|
||||
* @param {string} message - the message to log
|
||||
*/
|
||||
progress = (activityId, message) => {
|
||||
const toLog = {
|
||||
level: "info",
|
||||
message,
|
||||
}
|
||||
|
||||
if (typeof activityId === "string" && this.activities_[activityId]) {
|
||||
const activity = this.activities_[activityId]
|
||||
if (activity.activity) {
|
||||
activity.text = message
|
||||
} else {
|
||||
toLog["activity_id"] = activityId
|
||||
this.loggerInstance_.log(toLog)
|
||||
}
|
||||
} else {
|
||||
this.loggerInstance_.log(toLog)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error. If an error object is provided the stack trace for the error
|
||||
* will also be logged.
|
||||
* @param {String | Error} messageOrError - can either be a string with a
|
||||
* message to log the error under; or an error object.
|
||||
* @param {Error?} error - an error object to log message with
|
||||
*/
|
||||
error = (messageOrError, error = null) => {
|
||||
let message = messageOrError
|
||||
if (typeof messageOrError === "object") {
|
||||
message = messageOrError.message
|
||||
error = messageOrError
|
||||
}
|
||||
|
||||
const toLog = {
|
||||
level: "error",
|
||||
message,
|
||||
}
|
||||
|
||||
if (error) {
|
||||
toLog["stack"] = stackTrace.parse(error)
|
||||
}
|
||||
|
||||
this.loggerInstance_.log(toLog)
|
||||
|
||||
// Give stack traces and details in dev
|
||||
if (error && IS_DEV) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports failure of an activity. In development the activity will be udpated
|
||||
* with the failure message in other environments the failure will be logged
|
||||
* at the error level.
|
||||
* @param {string} activityId - the id of the activity as returned by activity
|
||||
* @param {string} message - the message to log
|
||||
* @returns {object} data about the activity
|
||||
*/
|
||||
failure = (activityId, message) => {
|
||||
const time = Date.now()
|
||||
const toLog = {
|
||||
level: "error",
|
||||
message,
|
||||
}
|
||||
|
||||
if (typeof activityId === "string" && this.activities_[activityId]) {
|
||||
const activity = this.activities_[activityId]
|
||||
if (activity.activity) {
|
||||
activity.activity.fail(`${message} – ${time - activity.start}`)
|
||||
} else {
|
||||
toLog["duration"] = time - activity.start
|
||||
toLog["activity_id"] = activityId
|
||||
this.loggerInstance_.log(toLog)
|
||||
}
|
||||
} else {
|
||||
this.loggerInstance_.log(toLog)
|
||||
}
|
||||
|
||||
if (this.activities_[activityId]) {
|
||||
const activity = this.activities_[activityId]
|
||||
return {
|
||||
...activity,
|
||||
duration: time - activity.start,
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports success of an activity. In development the activity will be udpated
|
||||
* with the failure message in other environments the failure will be logged
|
||||
* at the info level.
|
||||
* @param {string} activityId - the id of the activity as returned by activity
|
||||
* @param {string} message - the message to log
|
||||
* @returns {Record<string, any>} data about the activity
|
||||
*/
|
||||
success = (activityId, message) => {
|
||||
const time = Date.now()
|
||||
const toLog = {
|
||||
level: "info",
|
||||
message,
|
||||
}
|
||||
|
||||
if (typeof activityId === "string" && this.activities_[activityId]) {
|
||||
const activity = this.activities_[activityId]
|
||||
if (activity.activity) {
|
||||
activity.activity.succeed(`${message} – ${time - activity.start}ms`)
|
||||
} else {
|
||||
toLog["duration"] = time - activity.start
|
||||
toLog["activity_id"] = activityId
|
||||
this.loggerInstance_.log(toLog)
|
||||
}
|
||||
} else {
|
||||
this.loggerInstance_.log(toLog)
|
||||
}
|
||||
|
||||
if (this.activities_[activityId]) {
|
||||
const activity = this.activities_[activityId]
|
||||
return {
|
||||
...activity,
|
||||
duration: time - activity.start,
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message at the info level.
|
||||
* @param {string} message - the message to log
|
||||
*/
|
||||
debug = (message) => {
|
||||
this.loggerInstance_.log({
|
||||
level: "debug",
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message at the info level.
|
||||
* @param {string} message - the message to log
|
||||
*/
|
||||
info = (message) => {
|
||||
this.loggerInstance_.log({
|
||||
level: "info",
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message at the warn level.
|
||||
* @param {string} message - the message to log
|
||||
*/
|
||||
warn = (message) => {
|
||||
this.loggerInstance_.warn({
|
||||
level: "warn",
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around winston's log method.
|
||||
*/
|
||||
log = (...args) => {
|
||||
if (args.length > 1) {
|
||||
// @ts-ignore
|
||||
this.loggerInstance_.log(...args)
|
||||
} else {
|
||||
let message = args[0]
|
||||
this.loggerInstance_.log({
|
||||
level: "info",
|
||||
message,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const logger = new Reporter({
|
||||
logger: loggerInstance,
|
||||
activityLogger: ora,
|
||||
})
|
||||
|
||||
export default logger
|
||||
35
packages/cli/medusa-cli/src/reporter/panic-handler.ts
Normal file
35
packages/cli/medusa-cli/src/reporter/panic-handler.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export type PanicData = {
|
||||
id: string
|
||||
context: {
|
||||
rootPath: string
|
||||
path: string
|
||||
}
|
||||
}
|
||||
|
||||
export enum PanicId {
|
||||
InvalidProjectName = "10000",
|
||||
InvalidPath = "10002",
|
||||
AlreadyNodeProject = "10003",
|
||||
}
|
||||
|
||||
export const panicHandler = (panicData: PanicData = {} as PanicData) => {
|
||||
const { id, context } = panicData
|
||||
switch (id) {
|
||||
case "10000":
|
||||
return {
|
||||
message: `Looks like you provided a URL as your project name. Try "medusa new my-medusa-store ${context.rootPath}" instead.`,
|
||||
}
|
||||
case "10002":
|
||||
return {
|
||||
message: `Could not create project because ${context.path} is not a valid path.`,
|
||||
}
|
||||
case "10003":
|
||||
return {
|
||||
message: `Directory ${context.rootPath} is already a Node project.`,
|
||||
}
|
||||
default:
|
||||
return {
|
||||
message: "Unknown error",
|
||||
}
|
||||
}
|
||||
}
|
||||
25
packages/cli/medusa-cli/src/util/clear-project.ts
Normal file
25
packages/cli/medusa-cli/src/util/clear-project.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import fs from "fs"
|
||||
import glob from "glob"
|
||||
import path from "path"
|
||||
|
||||
export function clearProject(directory: string) {
|
||||
const adminFiles = glob.sync(path.join(directory, `src`, `admin/**/*`))
|
||||
const onboardingFiles = glob.sync(
|
||||
path.join(directory, `src`, `**/onboarding/`)
|
||||
)
|
||||
const typeFiles = glob.sync(path.join(directory, `src`, `types`))
|
||||
const srcFiles = glob.sync(
|
||||
path.join(directory, `src`, `**/*.{ts,tsx,js,jsx}`)
|
||||
)
|
||||
|
||||
const files = [...adminFiles, ...onboardingFiles, ...typeFiles, ...srcFiles]
|
||||
|
||||
files.forEach((file) =>
|
||||
fs.rmSync(file, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
})
|
||||
)
|
||||
// add empty typescript file to avoid build errors
|
||||
fs.openSync(path.join(directory, "src", "index.ts"), "w")
|
||||
}
|
||||
15
packages/cli/medusa-cli/src/util/package-manager.ts
Normal file
15
packages/cli/medusa-cli/src/util/package-manager.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import ConfigStore from "configstore"
|
||||
import reporter from "../reporter"
|
||||
|
||||
const config = new ConfigStore(`medusa`, {}, { globalConfigPath: true })
|
||||
|
||||
const packageMangerConfigKey = `cli.packageManager`
|
||||
|
||||
export const getPackageManager = () => {
|
||||
return config.get(packageMangerConfigKey)
|
||||
}
|
||||
|
||||
export const setPackageManager = (packageManager) => {
|
||||
config.set(packageMangerConfigKey, packageManager)
|
||||
reporter.info(`Preferred package manager set to "${packageManager}"`)
|
||||
}
|
||||
5
packages/cli/medusa-cli/src/util/version.ts
Normal file
5
packages/cli/medusa-cli/src/util/version.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { getMedusaVersion } from "medusa-core-utils"
|
||||
|
||||
export const getLocalMedusaVersion = (): string => {
|
||||
return getMedusaVersion()
|
||||
}
|
||||
Reference in New Issue
Block a user