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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user