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:
1 parent
aecae5705d
commit
240b038006
7 files changed
+172
-9
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa-cli": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(medusa-cli): remove .git directory in `new` command
|
||||||
@@ -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 { displayFactBox, FactBoxOptions } from "../utils/facts.js"
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { runCloneRepo } from "../utils/clone-repo.js"
|
import { runCloneRepo } from "../utils/clone-repo.js"
|
||||||
|
import {
|
||||||
|
askForNextjsStarter,
|
||||||
|
installNextjsStarter,
|
||||||
|
startNextjsStarter,
|
||||||
|
} from "../utils/nextjs-utils.js"
|
||||||
|
|
||||||
const slugify = slugifyType.default
|
const slugify = slugifyType.default
|
||||||
const isEmail = isEmailImported.default
|
const isEmail = isEmailImported.default
|
||||||
@@ -36,6 +41,7 @@ export type CreateOptions = {
|
|||||||
browser?: boolean
|
browser?: boolean
|
||||||
migrations?: boolean
|
migrations?: boolean
|
||||||
directoryPath?: string
|
directoryPath?: string
|
||||||
|
withNextjsStarter?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async ({
|
export default async ({
|
||||||
@@ -47,6 +53,7 @@ export default async ({
|
|||||||
browser,
|
browser,
|
||||||
migrations,
|
migrations,
|
||||||
directoryPath,
|
directoryPath,
|
||||||
|
withNextjsStarter = false,
|
||||||
}: CreateOptions) => {
|
}: CreateOptions) => {
|
||||||
track("CREATE_CLI")
|
track("CREATE_CLI")
|
||||||
if (repoUrl) {
|
if (repoUrl) {
|
||||||
@@ -70,6 +77,7 @@ export default async ({
|
|||||||
let isProjectCreated = false
|
let isProjectCreated = false
|
||||||
let isDbInitialized = false
|
let isDbInitialized = false
|
||||||
let printedMessage = false
|
let printedMessage = false
|
||||||
|
let nextjsDirectory = ""
|
||||||
|
|
||||||
processManager.onTerminated(async () => {
|
processManager.onTerminated(async () => {
|
||||||
spinner.stop()
|
spinner.stop()
|
||||||
@@ -83,7 +91,7 @@ export default async ({
|
|||||||
// this ensures that the message isn't printed twice to the user
|
// this ensures that the message isn't printed twice to the user
|
||||||
if (!printedMessage && isProjectCreated) {
|
if (!printedMessage && isProjectCreated) {
|
||||||
printedMessage = true
|
printedMessage = true
|
||||||
showSuccessMessage(projectName)
|
showSuccessMessage(projectName, undefined, nextjsDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -93,6 +101,7 @@ export default async ({
|
|||||||
const projectPath = getProjectPath(projectName, directoryPath)
|
const projectPath = getProjectPath(projectName, directoryPath)
|
||||||
const adminEmail =
|
const adminEmail =
|
||||||
!skipDb && migrations ? await askForAdminEmail(seed, boilerplate) : ""
|
!skipDb && migrations ? await askForAdminEmail(seed, boilerplate) : ""
|
||||||
|
const installNextjs = withNextjsStarter || (await askForNextjsStarter())
|
||||||
|
|
||||||
const { client, dbConnectionString } = !skipDb
|
const { client, dbConnectionString } = !skipDb
|
||||||
? await getDbClientAndCredentials({
|
? await getDbClientAndCredentials({
|
||||||
@@ -131,6 +140,14 @@ export default async ({
|
|||||||
message: "Created project directory",
|
message: "Created project directory",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
nextjsDirectory = installNextjs
|
||||||
|
? await installNextjsStarter({
|
||||||
|
directoryName: projectPath,
|
||||||
|
abortController,
|
||||||
|
factBoxOptions,
|
||||||
|
})
|
||||||
|
: ""
|
||||||
|
|
||||||
if (client && !dbUrl) {
|
if (client && !dbUrl) {
|
||||||
factBoxOptions.interval = displayFactBox({
|
factBoxOptions.interval = displayFactBox({
|
||||||
...factBoxOptions,
|
...factBoxOptions,
|
||||||
@@ -160,6 +177,8 @@ export default async ({
|
|||||||
abortController,
|
abortController,
|
||||||
skipDb,
|
skipDb,
|
||||||
migrations,
|
migrations,
|
||||||
|
onboardingType: installNextjs ? "nextjs" : "default",
|
||||||
|
nextjsDirectory,
|
||||||
})
|
})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (isAbortError(e)) {
|
if (isAbortError(e)) {
|
||||||
@@ -181,7 +200,7 @@ export default async ({
|
|||||||
spinner.succeed(chalk.green("Project Prepared"))
|
spinner.succeed(chalk.green("Project Prepared"))
|
||||||
|
|
||||||
if (skipDb || !browser) {
|
if (skipDb || !browser) {
|
||||||
showSuccessMessage(projectPath, inviteToken)
|
showSuccessMessage(projectPath, inviteToken, nextjsDirectory)
|
||||||
process.exit()
|
process.exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +214,13 @@ export default async ({
|
|||||||
directory: projectPath,
|
directory: projectPath,
|
||||||
abortController,
|
abortController,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (installNextjs && nextjsDirectory) {
|
||||||
|
void startNextjsStarter({
|
||||||
|
directory: nextjsDirectory,
|
||||||
|
abortController,
|
||||||
|
})
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (isAbortError(e)) {
|
if (isAbortError(e)) {
|
||||||
process.exit()
|
process.exit()
|
||||||
@@ -229,7 +255,7 @@ async function askForProjectName(directoryPath?: string): Promise<string> {
|
|||||||
message: "What's the name of your project?",
|
message: "What's the name of your project?",
|
||||||
default: "my-medusa-store",
|
default: "my-medusa-store",
|
||||||
filter: (input) => {
|
filter: (input) => {
|
||||||
return slugify(input)
|
return slugify(input).toLowerCase()
|
||||||
},
|
},
|
||||||
validate: (input) => {
|
validate: (input) => {
|
||||||
if (!input.length) {
|
if (!input.length) {
|
||||||
@@ -267,12 +293,16 @@ async function askForAdminEmail(
|
|||||||
return adminEmail
|
return adminEmail
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSuccessMessage(projectName: string, inviteToken?: string) {
|
function showSuccessMessage(
|
||||||
|
projectName: string,
|
||||||
|
inviteToken?: string,
|
||||||
|
nextjsDirectory?: string
|
||||||
|
) {
|
||||||
logMessage({
|
logMessage({
|
||||||
message: boxen(
|
message: boxen(
|
||||||
chalk.green(
|
chalk.green(
|
||||||
// eslint-disable-next-line prettier/prettier
|
// 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",
|
titleAlignment: "center",
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ program
|
|||||||
"--directory-path <path>",
|
"--directory-path <path>",
|
||||||
"Specify the directory path to install the project in."
|
"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()
|
.parse()
|
||||||
|
|
||||||
void create(program.opts())
|
void create(program.opts())
|
||||||
@@ -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
|
abortController?: AbortController
|
||||||
skipDb?: boolean
|
skipDb?: boolean
|
||||||
migrations?: boolean
|
migrations?: boolean
|
||||||
|
onboardingType?: "default" | "nextjs"
|
||||||
|
nextjsDirectory?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async ({
|
export default async ({
|
||||||
@@ -34,6 +36,8 @@ export default async ({
|
|||||||
abortController,
|
abortController,
|
||||||
skipDb,
|
skipDb,
|
||||||
migrations,
|
migrations,
|
||||||
|
onboardingType = "default",
|
||||||
|
nextjsDirectory = "",
|
||||||
}: PrepareOptions) => {
|
}: PrepareOptions) => {
|
||||||
// initialize execution options
|
// initialize execution options
|
||||||
const execOptions = {
|
const execOptions = {
|
||||||
@@ -61,11 +65,12 @@ export default async ({
|
|||||||
let inviteToken: string | undefined = undefined
|
let inviteToken: string | undefined = undefined
|
||||||
|
|
||||||
if (!skipDb) {
|
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
|
// add connection string to project
|
||||||
fs.appendFileSync(
|
fs.appendFileSync(path.join(directory, `.env`), env)
|
||||||
path.join(directory, `.env`),
|
|
||||||
`DATABASE_TYPE=postgres${EOL}DATABASE_URL=${dbConnectionString}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
factBoxOptions.interval = displayFactBox({
|
factBoxOptions.interval = displayFactBox({
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import reporter from "../reporter"
|
|||||||
import { getPackageManager, setPackageManager } from "../util/package-manager"
|
import { getPackageManager, setPackageManager } from "../util/package-manager"
|
||||||
import { PanicId } from "../reporter/panic-handler"
|
import { PanicId } from "../reporter/panic-handler"
|
||||||
import { clearProject } from "../util/clear-project"
|
import { clearProject } from "../util/clear-project"
|
||||||
|
import path from "path"
|
||||||
|
|
||||||
const removeUndefined = (obj) => {
|
const removeUndefined = (obj) => {
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
@@ -641,6 +642,11 @@ medusa new ${rootPath} [url-to-starter]
|
|||||||
reporter.info("Final project preparations...")
|
reporter.info("Final project preparations...")
|
||||||
// remove demo files
|
// remove demo files
|
||||||
clearProject(rootPath)
|
clearProject(rootPath)
|
||||||
|
// remove .git directory
|
||||||
|
fs.rmSync(path.join(rootPath, '.git'), {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
successMessage(rootPath)
|
successMessage(rootPath)
|
||||||
|
|||||||
Reference in new issue
Block a user