Files
medusa-store/packages/cli/create-medusa-app/src/utils/prepare-project.ts

293 lines
7.2 KiB
TypeScript

import chalk from "chalk"
import fs from "fs"
import path from "path"
import { Ora } from "ora"
import execute from "./execute.js"
import { EOL } from "os"
import { displayFactBox, FactBoxOptions } from "./facts.js"
import ProcessManager from "./process-manager.js"
import { clearProject } from "./clear-project.js"
import type { Client } from "pg"
type PrepareOptions = {
directory: string
dbConnectionString: string
admin?: {
email: string
}
seed?: boolean
boilerplate?: boolean
spinner: Ora
processManager: ProcessManager
abortController?: AbortController
skipDb?: boolean
migrations?: boolean
onboardingType?: "default" | "nextjs"
nextjsDirectory?: string
client: Client | null
verbose?: boolean
v2?: boolean
}
export default async ({
directory,
dbConnectionString,
admin,
seed,
boilerplate,
spinner,
processManager,
abortController,
skipDb,
migrations,
onboardingType = "default",
nextjsDirectory = "",
client,
verbose = false,
v2 = false,
}: PrepareOptions) => {
// initialize execution options
const execOptions = {
cwd: directory,
signal: abortController?.signal,
}
const npxOptions = {
...execOptions,
env: {
...process.env,
npm_config_yes: "yes",
},
}
const factBoxOptions: FactBoxOptions = {
interval: null,
spinner,
processManager,
message: "",
title: "",
verbose,
}
// initialize the invite token to return
let inviteToken: string | undefined = undefined
if (!skipDb) {
let env = `DATABASE_TYPE=postgres${EOL}DATABASE_URL=${dbConnectionString}${EOL}MEDUSA_ADMIN_ONBOARDING_TYPE=${onboardingType}${EOL}STORE_CORS=http://localhost:8000,http://localhost:7001`
if (v2) {
env += `${EOL}POSTGRES_URL=${dbConnectionString}`
}
if (nextjsDirectory) {
env += `${EOL}MEDUSA_ADMIN_ONBOARDING_NEXTJS_DIRECTORY=${nextjsDirectory}`
}
// add connection string to project
fs.appendFileSync(path.join(directory, `.env`), env)
}
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
spinner,
title: "Installing dependencies...",
processManager,
})
await processManager.runProcess({
process: async () => {
try {
await execute([`yarn`, execOptions], { verbose })
} catch (e) {
// yarn isn't available
// use npm
await execute([`npm install --legacy-peer-deps`, execOptions], {
verbose,
})
}
},
ignoreERESOLVE: true,
})
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
message: "Installed Dependencies",
})
if (!boilerplate) {
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
title: "Preparing Project Directory...",
})
// delete files and directories related to onboarding
clearProject(directory)
displayFactBox({
...factBoxOptions,
message: "Prepared Project Directory",
})
}
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
title: "Building Project...",
})
await processManager.runProcess({
process: async () => {
try {
await execute([`yarn build`, execOptions], { verbose })
} catch (e) {
// yarn isn't available
// use npm
await execute([`npm run build`, execOptions], { verbose })
}
},
ignoreERESOLVE: true,
})
displayFactBox({ ...factBoxOptions, message: "Project Built" })
if (!skipDb && migrations) {
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
title: "Running Migrations...",
})
// run migrations
await processManager.runProcess({
process: async () => {
const proc = await execute(
["npx @medusajs/medusa-cli@latest migrations run", npxOptions],
{ verbose, needOutput: true }
)
if (client) {
// check the migrations table is in the database
// to ensure that migrations ran
let errorOccurred = false
try {
const migrations = await client.query(
`SELECT * FROM "${v2 ? "mikro_orm_migrations" : "migrations"}"`
)
errorOccurred = migrations.rowCount == 0
} catch (e) {
// avoid error thrown if the migrations table
// doesn't exist
errorOccurred = true
}
// ensure that migrations actually ran in case of an uncaught error
if (errorOccurred && (proc.stderr || proc.stdout)) {
throw new Error(
`An error occurred while running migrations: ${
proc.stderr || proc.stdout
}`
)
}
}
},
})
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
message: "Ran Migrations",
})
}
if (admin && !skipDb && migrations && !v2) {
// create admin user
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
title: "Creating an admin user...",
})
await processManager.runProcess({
process: async () => {
const proc = await execute(
[
`npx @medusajs/medusa-cli@latest user -e ${admin.email} --invite`,
npxOptions,
],
{ verbose, needOutput: true }
)
// get invite token from stdout
const match = (proc.stdout as string).match(
/Invite token: (?<token>.+)/
)
inviteToken = match?.groups?.token
},
})
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
message: "Created admin user",
})
}
if (!skipDb && migrations) {
if (seed || !boilerplate) {
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
title: "Seeding database...",
})
// check if a seed file exists in the project
if (!fs.existsSync(path.join(directory, "data", "seed.json"))) {
spinner
?.warn(
chalk.yellow(
"Seed file was not found in the project. Skipping seeding..."
)
)
.start()
return inviteToken
}
await processManager.runProcess({
process: async () => {
await execute(
[
`npx @medusajs/medusa-cli@latest seed --seed-file=${path.join(
"data",
"seed.json"
)}`,
npxOptions,
],
{ verbose }
)
},
})
displayFactBox({
...factBoxOptions,
message: "Seeded database with demo data",
})
} else if (
fs.existsSync(path.join(directory, "data", "seed-onboarding.json"))
) {
// seed the database with onboarding seed
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
title: "Finish preparation...",
})
await processManager.runProcess({
process: async () => {
await execute(
[
`npx @medusajs/medusa-cli@latest seed --seed-file=${path.join(
"data",
"seed-onboarding.json"
)}`,
npxOptions,
],
{ verbose }
)
},
})
}
displayFactBox({ ...factBoxOptions, message: "Finished Preparation" })
}
return inviteToken
}