feat(create-medusa-app): add database options (#4733)
## What Adds new options for easier usage of the `create-medusa-app` command for regular medusa users. The following options are added: - `--skip-db`: Skips creating the database, running migrations, and seeding, and subsequently skips opening the browser. Useful if the developer wants to set the database URL at a later point in the configurations. - `--db-url <url>`: Skips database creation and sets the database URL to the provided URL. Throws an error if can't connect to the database. Will still run migrations and open the admin after project creation. Useful if the developer already has database created, locally or remotely. - `--no-migrations`: Skips running migrations, creating admin user, and seeding. If used, it's expected that you pass the `--db-url` option with a url of a database that has all necessary migrations. Otherwise, unexpected errors will occur. Helpful only if combined with `--db-url` - `--no-browser`: Disables opening the browser at the end of the project creation and only shows success message. - `--directory-path <path>`: Allows specifying the directory path to install the project in. Useful for testing.
This commit is contained in:
@@ -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")
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export async function runCreateDb({
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDbClientAndCredentials(dbName: string): Promise<{
|
||||
async function getForDbName(dbName: string): Promise<{
|
||||
client: pg.Client
|
||||
dbConnectionString: string
|
||||
}> {
|
||||
@@ -101,3 +101,40 @@ export async function getDbClientAndCredentials(dbName: string): Promise<{
|
||||
dbConnectionString,
|
||||
}
|
||||
}
|
||||
|
||||
async function getForDbUrl(dbUrl: string): Promise<{
|
||||
client: pg.Client
|
||||
dbConnectionString: string
|
||||
}> {
|
||||
let client!: pg.Client
|
||||
|
||||
try {
|
||||
client = await postgresClient({
|
||||
connectionString: dbUrl,
|
||||
})
|
||||
} catch (e) {
|
||||
logMessage({
|
||||
message: `Couldn't connect to PostgreSQL using the database URL you passed. Make sure it's correct and try again.`,
|
||||
type: "error",
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
client,
|
||||
dbConnectionString: dbUrl,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDbClientAndCredentials({
|
||||
dbName = "",
|
||||
dbUrl = "",
|
||||
}): Promise<{
|
||||
client: pg.Client
|
||||
dbConnectionString: string
|
||||
}> {
|
||||
if (dbName) {
|
||||
return await getForDbName(dbName)
|
||||
} else {
|
||||
return await getForDbUrl(dbUrl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ const { Client } = pg
|
||||
type PostgresConnection = {
|
||||
user?: string
|
||||
password?: string
|
||||
connectionString?: string
|
||||
}
|
||||
|
||||
export default async (connect: PostgresConnection) => {
|
||||
|
||||
@@ -5,8 +5,8 @@ import { Ora } from "ora"
|
||||
import promiseExec from "./promise-exec.js"
|
||||
import { EOL } from "os"
|
||||
import { displayFactBox, FactBoxOptions } from "./facts.js"
|
||||
import { clearProject } from "@medusajs/utils"
|
||||
import ProcessManager from "./process-manager.js"
|
||||
import { clearProject } from "./clear-project.js"
|
||||
|
||||
type PrepareOptions = {
|
||||
directory: string
|
||||
@@ -19,6 +19,8 @@ type PrepareOptions = {
|
||||
spinner: Ora
|
||||
processManager: ProcessManager
|
||||
abortController?: AbortController
|
||||
skipDb?: boolean
|
||||
migrations?: boolean
|
||||
}
|
||||
|
||||
export default async ({
|
||||
@@ -30,6 +32,8 @@ export default async ({
|
||||
spinner,
|
||||
processManager,
|
||||
abortController,
|
||||
skipDb,
|
||||
migrations,
|
||||
}: PrepareOptions) => {
|
||||
// initialize execution options
|
||||
const execOptions = {
|
||||
@@ -56,11 +60,13 @@ export default async ({
|
||||
// initialize the invite token to return
|
||||
let inviteToken: string | undefined = undefined
|
||||
|
||||
// add connection string to project
|
||||
fs.appendFileSync(
|
||||
path.join(directory, `.env`),
|
||||
`DATABASE_TYPE=postgres${EOL}DATABASE_URL=${dbConnectionString}`
|
||||
)
|
||||
if (!skipDb) {
|
||||
// add connection string to project
|
||||
fs.appendFileSync(
|
||||
path.join(directory, `.env`),
|
||||
`DATABASE_TYPE=postgres${EOL}DATABASE_URL=${dbConnectionString}`
|
||||
)
|
||||
}
|
||||
|
||||
factBoxOptions.interval = displayFactBox({
|
||||
...factBoxOptions,
|
||||
@@ -119,36 +125,39 @@ export default async ({
|
||||
})
|
||||
|
||||
displayFactBox({ ...factBoxOptions, message: "Project Built" })
|
||||
factBoxOptions.interval = displayFactBox({
|
||||
...factBoxOptions,
|
||||
title: "Running Migrations...",
|
||||
})
|
||||
|
||||
// run migrations
|
||||
await processManager.runProcess({
|
||||
process: async () => {
|
||||
const proc = await promiseExec(
|
||||
"npx @medusajs/medusa-cli@latest migrations run",
|
||||
npxOptions
|
||||
)
|
||||
if (!skipDb && migrations) {
|
||||
factBoxOptions.interval = displayFactBox({
|
||||
...factBoxOptions,
|
||||
title: "Running Migrations...",
|
||||
})
|
||||
|
||||
// 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
|
||||
}`
|
||||
// run migrations
|
||||
await processManager.runProcess({
|
||||
process: async () => {
|
||||
const proc = await promiseExec(
|
||||
"npx @medusajs/medusa-cli@latest migrations run",
|
||||
npxOptions
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
factBoxOptions.interval = displayFactBox({
|
||||
...factBoxOptions,
|
||||
message: "Ran Migrations",
|
||||
})
|
||||
// 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
|
||||
}`
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
if (admin) {
|
||||
factBoxOptions.interval = displayFactBox({
|
||||
...factBoxOptions,
|
||||
message: "Ran Migrations",
|
||||
})
|
||||
}
|
||||
|
||||
if (admin && !skipDb && migrations) {
|
||||
// create admin user
|
||||
factBoxOptions.interval = displayFactBox({
|
||||
...factBoxOptions,
|
||||
@@ -173,61 +182,63 @@ export default async ({
|
||||
})
|
||||
}
|
||||
|
||||
if (seed || !boilerplate) {
|
||||
factBoxOptions.interval = displayFactBox({
|
||||
...factBoxOptions,
|
||||
title: "Seeding database...",
|
||||
})
|
||||
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..."
|
||||
// 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
|
||||
.start()
|
||||
return inviteToken
|
||||
}
|
||||
|
||||
await processManager.runProcess({
|
||||
process: async () => {
|
||||
await promiseExec(
|
||||
`npx @medusajs/medusa-cli@latest seed --seed-file=${path.join(
|
||||
"data",
|
||||
"seed.json"
|
||||
)}`,
|
||||
npxOptions
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
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 promiseExec(
|
||||
`npx @medusajs/medusa-cli@latest seed --seed-file=${path.join(
|
||||
"data",
|
||||
"seed-onboarding.json"
|
||||
)}`,
|
||||
npxOptions
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
await processManager.runProcess({
|
||||
process: async () => {
|
||||
await promiseExec(
|
||||
`npx @medusajs/medusa-cli@latest seed --seed-file=${path.join(
|
||||
"data",
|
||||
"seed.json"
|
||||
)}`,
|
||||
npxOptions
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
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 promiseExec(
|
||||
`npx @medusajs/medusa-cli@latest seed --seed-file=${path.join(
|
||||
"data",
|
||||
"seed-onboarding.json"
|
||||
)}`,
|
||||
npxOptions
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
displayFactBox({ ...factBoxOptions, message: "Finished Preparation" })
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user