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:
5
.changeset/nine-peas-draw.md
Normal file
5
.changeset/nine-peas-draw.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@medusajs/medusa-cli": patch
|
||||
---
|
||||
|
||||
fix(medusa-cli): remove .git directory in `new` command
|
||||
5
.changeset/two-kids-thank.md
Normal file
5
.changeset/two-kids-thank.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-medusa-app": minor
|
||||
---
|
||||
|
||||
feat(create-medusa-app): add install Next.js storefront option
|
||||
@@ -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",
|
||||
|
||||
@@ -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())
|
||||
|
||||
107
packages/create-medusa-app/src/utils/nextjs-utils.ts
Normal file
107
packages/create-medusa-app/src/utils/nextjs-utils.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user