feat(create-medusa-app,medusa-cli): Allow clearing project (#4273)

- Added build step
- Added `--no-boilerplate` option to `create-medusa-app` to allow clearing onboarding files
- Clear project files by default in medusa-cli
This commit is contained in:
Shahed Nasser
2023-06-15 17:31:30 +00:00
committed by GitHub
parent f8643361cd
commit f98ba5bde8
18 changed files with 582 additions and 87 deletions
@@ -6,7 +6,6 @@ type CloneRepoOptions = {
abortController?: AbortController
}
// TODO change default repo URL
const DEFAULT_REPO = "https://github.com/medusajs/medusa-starter-default"
export default async ({
@@ -1,8 +1,8 @@
import onProcessTerminated from "./on-process-terminated.js"
import ProcessManager from "./process-manager.js"
export default () => {
export default (processManager: ProcessManager) => {
const abortController = new AbortController()
onProcessTerminated(() => abortController.abort())
processManager.onTerminated(() => abortController.abort())
return abortController
}
+11 -6
View File
@@ -1,7 +1,8 @@
import boxen from "boxen"
import chalk from "chalk"
import { Ora } from "ora"
import onProcessTerminated from "./on-process-terminated.js"
import { emojify } from "node-emoji"
import ProcessManager from "./process-manager.js"
const facts = [
"Plugins allow you to integrate third-party services for payment, fulfillment, notifications, and more.",
@@ -31,17 +32,20 @@ export const getFact = () => {
export const showFact = (spinner: Ora, title: string) => {
const fact = getFact()
spinner.text = `${boxen(fact, {
spinner.text = `${boxen(`${emojify(":bulb:")} Medusa Tips\n\n${fact}`, {
title: chalk.cyan(title),
titleAlignment: "center",
textAlignment: "center",
padding: 1,
margin: 1,
float: "center",
})}`
}
export const createFactBox = (spinner: Ora, title: string): NodeJS.Timer => {
export const createFactBox = (
spinner: Ora,
title: string,
processManager: ProcessManager
): NodeJS.Timer => {
spinner.spinner = {
frames: [""],
}
@@ -50,7 +54,7 @@ export const createFactBox = (spinner: Ora, title: string): NodeJS.Timer => {
showFact(spinner, title)
}, 10000)
onProcessTerminated(() => clearInterval(interval))
processManager.addInterval(interval)
return interval
}
@@ -59,6 +63,7 @@ export const resetFactBox = (
interval: NodeJS.Timer | null,
spinner: Ora,
successMessage: string,
processManager: ProcessManager,
newTitle?: string
): NodeJS.Timer | null => {
if (interval) {
@@ -69,7 +74,7 @@ export const resetFactBox = (
spinner.succeed(chalk.green(successMessage)).start()
let newInterval = null
if (newTitle) {
newInterval = createFactBox(spinner, newTitle)
newInterval = createFactBox(spinner, newTitle, processManager)
}
return newInterval
@@ -1,4 +0,0 @@
export default (fn: Function) => {
process.on("SIGTERM", () => fn())
process.on("SIGINT", () => fn())
}
@@ -4,8 +4,9 @@ import path from "path"
import { Ora } from "ora"
import promiseExec from "./promise-exec.js"
import { EOL } from "os"
import runProcess from "./run-process.js"
import { createFactBox, resetFactBox } from "./facts.js"
import { clearProject } from "@medusajs/utils"
import ProcessManager from "./process-manager.js"
type PrepareOptions = {
directory: string
@@ -14,7 +15,9 @@ type PrepareOptions = {
email: string
}
seed?: boolean
boilerplate?: boolean
spinner: Ora
processManager: ProcessManager
abortController?: AbortController
}
@@ -23,7 +26,9 @@ export default async ({
dbConnectionString,
admin,
seed,
boilerplate,
spinner,
processManager,
abortController,
}: PrepareOptions) => {
// initialize execution options
@@ -43,10 +48,11 @@ export default async ({
let interval: NodeJS.Timer | null = createFactBox(
spinner,
"Installing dependencies..."
"Installing dependencies...",
processManager
)
await runProcess({
await processManager.runProcess({
process: async () => {
try {
await promiseExec(`yarn`, execOptions)
@@ -63,26 +69,73 @@ export default async ({
interval,
spinner,
"Installed Dependencies",
"Running Migrations...."
processManager
)
// run migrations
await runProcess({
if (!boilerplate) {
interval = createFactBox(
spinner,
"Preparing Project Directory...",
processManager
)
// delete files and directories related to onboarding
clearProject(directory)
interval = resetFactBox(
interval,
spinner,
"Prepared Project Directory",
processManager
)
}
interval = createFactBox(spinner, "Building Project...", processManager)
await processManager.runProcess({
process: async () => {
await promiseExec(
try {
await promiseExec(`yarn build`, execOptions)
} catch (e) {
// yarn isn't available
// use npm
await promiseExec(`npm run build`, execOptions)
}
},
ignoreERESOLVE: true,
})
interval = resetFactBox(interval, spinner, "Project Built", processManager)
interval = createFactBox(spinner, "Running Migrations...", processManager)
// run migrations
await processManager.runProcess({
process: async () => {
const proc = await promiseExec(
"npx -y @medusajs/medusa-cli@latest migrations run",
execOptions
)
// ensure that migrations actually ran in case of an uncaught error
if (!proc.stdout.includes("Migrations completed")) {
throw new Error(
`An error occurred while running migrations: ${
proc.stderr || proc.stdout
}`
)
}
},
})
interval = resetFactBox(interval, spinner, "Ran Migrations")
interval = resetFactBox(interval, spinner, "Ran Migrations", processManager)
if (admin) {
// create admin user
interval = createFactBox(spinner, "Creating an admin user...")
interval = createFactBox(
spinner,
"Creating an admin user...",
processManager
)
await runProcess({
await processManager.runProcess({
process: async () => {
const proc = await promiseExec(
`npx -y @medusajs/medusa-cli@latest user -e ${admin.email} --invite`,
@@ -94,11 +147,16 @@ export default async ({
},
})
interval = resetFactBox(interval, spinner, "Created admin user")
interval = resetFactBox(
interval,
spinner,
"Created admin user",
processManager
)
}
if (seed) {
interval = createFactBox(spinner, "Seeding database...")
if (seed || !boilerplate) {
interval = createFactBox(spinner, "Seeding database...", processManager)
// check if a seed file exists in the project
if (!fs.existsSync(path.join(directory, "data", "seed.json"))) {
@@ -112,7 +170,7 @@ export default async ({
return inviteToken
}
await runProcess({
await processManager.runProcess({
process: async () => {
await promiseExec(
`npx -y @medusajs/medusa-cli@latest seed --seed-file=${path.join(
@@ -123,14 +181,19 @@ export default async ({
)
},
})
resetFactBox(interval, spinner, "Seeded database with demo data")
resetFactBox(
interval,
spinner,
"Seeded database with demo data",
processManager
)
} else if (
fs.existsSync(path.join(directory, "data", "seed-onboarding.json"))
) {
// seed the database with onboarding seed
interval = createFactBox(spinner, "Finish preparation...")
interval = createFactBox(spinner, "Finish preparation...", processManager)
await runProcess({
await processManager.runProcess({
process: async () => {
await promiseExec(
`npx -y @medusajs/medusa-cli@latest seed --seed-file=${path.join(
@@ -141,7 +204,7 @@ export default async ({
)
},
})
resetFactBox(interval, spinner, "Finished Preparation")
resetFactBox(interval, spinner, "Finished Preparation", processManager)
}
return inviteToken
@@ -0,0 +1,60 @@
type ProcessOptions = {
process: Function
ignoreERESOLVE?: boolean
}
export default class ProcessManager {
intervals: NodeJS.Timer[] = []
static MAX_RETRIES = 3
constructor() {
this.onTerminated(() => {
this.intervals.forEach((interval) => {
clearInterval(interval)
})
})
}
onTerminated(fn: Function) {
process.on("SIGTERM", () => fn())
process.on("SIGINT", () => fn())
}
addInterval(interval: NodeJS.Timer) {
this.intervals.push(interval)
}
// when running commands with npx or npm sometimes they
// terminate with EAGAIN error unexpectedly
// this utility function allows retrying the process if
// EAGAIN occurs, or otherwise throw the error that occurs
async runProcess({ process, ignoreERESOLVE }: ProcessOptions) {
let processError = false
let retries = 0
do {
retries++
try {
await process()
} catch (error) {
if (
typeof error === "object" &&
error !== null &&
"code" in error &&
error?.code === "EAGAIN"
) {
processError = true
} else if (
ignoreERESOLVE &&
typeof error === "object" &&
error !== null &&
"code" in error &&
error?.code === "ERESOLVE"
) {
// ignore error
} else {
throw error
}
}
} while (processError && retries <= ProcessManager.MAX_RETRIES)
}
}
@@ -1,36 +0,0 @@
type ProcessOptions = {
process: Function
ignoreERESOLVE?: boolean
}
// when running commands with npx or npm sometimes they
// terminate with EAGAIN error unexpectedly
// this utility function allows retrying the process if
// EAGAIN occurs, or otherwise throw the error that occurs
export default async ({ process, ignoreERESOLVE }: ProcessOptions) => {
let processError = false
do {
try {
await process()
} catch (error) {
if (
typeof error === "object" &&
error !== null &&
"code" in error &&
error?.code === "EAGAIN"
) {
processError = true
} else if (
ignoreERESOLVE &&
typeof error === "object" &&
error !== null &&
"code" in error &&
error?.code === "ERESOLVE"
) {
// ignore error
} else {
throw error
}
}
} while (processError)
}