feat(create-medusa-app): add install Next.js storefront option (#4968)

* feat(create-medusa-app): add install Next.js storefront option

* added config for yes option

* added more instructions to exit message

* handle duplicate directories and next errors

* use next.js branch

* add line break in storefront question

* pass next.js directory name as env variable

* change question message

* address PR feedback

* fix(medusa-cli): remove .git directory in `new` command

* fix deleting a directory error
This commit is contained in:
Shahed Nasser
2023-09-08 13:17:48 +03:00
committed by GitHub
parent aecae5705d
commit 240b038006
7 changed files with 172 additions and 9 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/medusa-cli": patch
---
fix(medusa-cli): remove .git directory in `new` command

View File

@@ -0,0 +1,5 @@
---
"create-medusa-app": minor
---
feat(create-medusa-app): add install Next.js storefront option

View File

@@ -22,6 +22,11 @@ import { nanoid } from "nanoid"
import { displayFactBox, FactBoxOptions } from "../utils/facts.js"
import { EOL } from "os"
import { runCloneRepo } from "../utils/clone-repo.js"
import {
askForNextjsStarter,
installNextjsStarter,
startNextjsStarter,
} from "../utils/nextjs-utils.js"
const slugify = slugifyType.default
const isEmail = isEmailImported.default
@@ -36,6 +41,7 @@ export type CreateOptions = {
browser?: boolean
migrations?: boolean
directoryPath?: string
withNextjsStarter?: boolean
}
export default async ({
@@ -47,6 +53,7 @@ export default async ({
browser,
migrations,
directoryPath,
withNextjsStarter = false,
}: CreateOptions) => {
track("CREATE_CLI")
if (repoUrl) {
@@ -70,6 +77,7 @@ export default async ({
let isProjectCreated = false
let isDbInitialized = false
let printedMessage = false
let nextjsDirectory = ""
processManager.onTerminated(async () => {
spinner.stop()
@@ -83,7 +91,7 @@ export default async ({
// this ensures that the message isn't printed twice to the user
if (!printedMessage && isProjectCreated) {
printedMessage = true
showSuccessMessage(projectName)
showSuccessMessage(projectName, undefined, nextjsDirectory)
}
return
@@ -93,6 +101,7 @@ export default async ({
const projectPath = getProjectPath(projectName, directoryPath)
const adminEmail =
!skipDb && migrations ? await askForAdminEmail(seed, boilerplate) : ""
const installNextjs = withNextjsStarter || (await askForNextjsStarter())
const { client, dbConnectionString } = !skipDb
? await getDbClientAndCredentials({
@@ -131,6 +140,14 @@ export default async ({
message: "Created project directory",
})
nextjsDirectory = installNextjs
? await installNextjsStarter({
directoryName: projectPath,
abortController,
factBoxOptions,
})
: ""
if (client && !dbUrl) {
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
@@ -160,6 +177,8 @@ export default async ({
abortController,
skipDb,
migrations,
onboardingType: installNextjs ? "nextjs" : "default",
nextjsDirectory,
})
} catch (e: any) {
if (isAbortError(e)) {
@@ -181,7 +200,7 @@ export default async ({
spinner.succeed(chalk.green("Project Prepared"))
if (skipDb || !browser) {
showSuccessMessage(projectPath, inviteToken)
showSuccessMessage(projectPath, inviteToken, nextjsDirectory)
process.exit()
}
@@ -195,6 +214,13 @@ export default async ({
directory: projectPath,
abortController,
})
if (installNextjs && nextjsDirectory) {
void startNextjsStarter({
directory: nextjsDirectory,
abortController,
})
}
} catch (e) {
if (isAbortError(e)) {
process.exit()
@@ -229,7 +255,7 @@ async function askForProjectName(directoryPath?: string): Promise<string> {
message: "What's the name of your project?",
default: "my-medusa-store",
filter: (input) => {
return slugify(input)
return slugify(input).toLowerCase()
},
validate: (input) => {
if (!input.length) {
@@ -267,12 +293,16 @@ async function askForAdminEmail(
return adminEmail
}
function showSuccessMessage(projectName: string, inviteToken?: string) {
function showSuccessMessage(
projectName: string,
inviteToken?: string,
nextjsDirectory?: string
) {
logMessage({
message: boxen(
chalk.green(
// eslint-disable-next-line prettier/prettier
`Change to the \`${projectName}\` directory to explore your Medusa project.${EOL}${EOL}Start your Medusa app again with the following command:${EOL}${EOL}npx @medusajs/medusa-cli develop${EOL}${EOL}${inviteToken ? `${EOL}${EOL}After you start the Medusa app, you can set a password for your admin user with the URL ${getInviteUrl(inviteToken)}${EOL}${EOL}` : ""}Check out the Medusa documentation to start your development:${EOL}${EOL}https://docs.medusajs.com/${EOL}${EOL}Star us on GitHub if you like what we're building:${EOL}${EOL}https://github.com/medusajs/medusa/stargazers`
`Change to the \`${projectName}\` directory to explore your Medusa project.${EOL}${EOL}Start your Medusa app again with the following command:${EOL}${EOL}npx @medusajs/medusa-cli develop${EOL}${EOL}${inviteToken ? `After you start the Medusa app, you can set a password for your admin user with the URL ${getInviteUrl(inviteToken)}${EOL}${EOL}` : ""}${nextjsDirectory?.length ? `The Next.js Starter storefront was installed in the \`${nextjsDirectory}\` directory. Change to that directory and start it with the following command:${EOL}${EOL}npm run dev${EOL}${EOL}` : ""}Check out the Medusa documentation to start your development:${EOL}${EOL}https://docs.medusajs.com/${EOL}${EOL}Star us on GitHub if you like what we're building:${EOL}${EOL}https://github.com/medusajs/medusa/stargazers`
),
{
titleAlignment: "center",

View File

@@ -33,6 +33,11 @@ program
"--directory-path <path>",
"Specify the directory path to install the project in."
)
.option(
"--with-nextjs-starter",
"Install the Next.js starter along with the Medusa backend",
false
)
.parse()
void create(program.opts())

View File

@@ -0,0 +1,107 @@
import inquirer from "inquirer"
import promiseExec from "./promise-exec.js"
import { FactBoxOptions, displayFactBox } from "./facts.js"
import fs from "fs"
import { customAlphabet, nanoid } from "nanoid"
import { isAbortError } from "./create-abort-controller.js"
import logMessage from "./log-message.js"
import { EOL } from "os"
const NEXTJS_REPO = "https://github.com/medusajs/nextjs-starter-medusa"
export async function askForNextjsStarter(): Promise<boolean> {
const { installNextjs } = await inquirer.prompt([
{
type: "confirm",
name: "installNextjs",
message: `Would you like to create the Next.js storefront? You can also create it later`,
default: false,
},
])
return installNextjs
}
type InstallOptions = {
directoryName: string
abortController?: AbortController
factBoxOptions: FactBoxOptions
}
export async function installNextjsStarter({
directoryName,
abortController,
factBoxOptions,
}: InstallOptions): Promise<string> {
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
title: "Installing Next.js Storefront...",
})
let nextjsDirectory = `${directoryName}-storefront`
if (
fs.existsSync(nextjsDirectory) &&
fs.lstatSync(nextjsDirectory).isDirectory()
) {
// append a random number to the directory name
nextjsDirectory += `-${customAlphabet(
// npm throws an error if the directory name has an uppercase letter
"123456789abcdefghijklmnopqrstuvwxyz",
4
)()}`
}
try {
await promiseExec(
`npx create-next-app -e ${NEXTJS_REPO} ${nextjsDirectory}`,
{
signal: abortController?.signal,
env: {
...process.env,
npm_config_yes: "yes",
},
}
)
} catch (e) {
if (isAbortError(e)) {
process.exit()
}
logMessage({
message: `An error occurred while installing Next.js storefront: ${e}`,
type: "error",
})
}
await promiseExec(`mv .env.template .env.local`, {
cwd: nextjsDirectory,
signal: abortController?.signal,
})
displayFactBox({
...factBoxOptions,
message: `Installed Next.js Starter successfully in the ${nextjsDirectory} directory.`,
})
return nextjsDirectory
}
type StartOptions = {
directory: string
abortController?: AbortController
}
export async function startNextjsStarter({
directory,
abortController,
}: StartOptions) {
try {
await promiseExec(`npm run dev`, {
cwd: directory,
signal: abortController?.signal,
})
} catch {
// ignore abort errors
}
}

View File

@@ -21,6 +21,8 @@ type PrepareOptions = {
abortController?: AbortController
skipDb?: boolean
migrations?: boolean
onboardingType?: "default" | "nextjs"
nextjsDirectory?: string
}
export default async ({
@@ -34,6 +36,8 @@ export default async ({
abortController,
skipDb,
migrations,
onboardingType = "default",
nextjsDirectory = "",
}: PrepareOptions) => {
// initialize execution options
const execOptions = {
@@ -61,11 +65,12 @@ export default async ({
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 (nextjsDirectory) {
env += `${EOL}MEDUSA_ADMIN_ONBOARDING_NEXTJS_DIRECTORY=${nextjsDirectory}`
}
// add connection string to project
fs.appendFileSync(
path.join(directory, `.env`),
`DATABASE_TYPE=postgres${EOL}DATABASE_URL=${dbConnectionString}`
)
fs.appendFileSync(path.join(directory, `.env`), env)
}
factBoxOptions.interval = displayFactBox({

View File

@@ -20,6 +20,7 @@ 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(
@@ -641,6 +642,11 @@ medusa new ${rootPath} [url-to-starter]
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)