diff --git a/.changeset/nine-peas-draw.md b/.changeset/nine-peas-draw.md new file mode 100644 index 0000000000..1c97dee94d --- /dev/null +++ b/.changeset/nine-peas-draw.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa-cli": patch +--- + +fix(medusa-cli): remove .git directory in `new` command diff --git a/.changeset/two-kids-thank.md b/.changeset/two-kids-thank.md new file mode 100644 index 0000000000..3807de98a9 --- /dev/null +++ b/.changeset/two-kids-thank.md @@ -0,0 +1,5 @@ +--- +"create-medusa-app": minor +--- + +feat(create-medusa-app): add install Next.js storefront option diff --git a/packages/create-medusa-app/src/commands/create.ts b/packages/create-medusa-app/src/commands/create.ts index ec9f4ee37c..9f52dbda7a 100644 --- a/packages/create-medusa-app/src/commands/create.ts +++ b/packages/create-medusa-app/src/commands/create.ts @@ -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 { 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", diff --git a/packages/create-medusa-app/src/index.ts b/packages/create-medusa-app/src/index.ts index 3fd716976c..35a63a756d 100644 --- a/packages/create-medusa-app/src/index.ts +++ b/packages/create-medusa-app/src/index.ts @@ -33,6 +33,11 @@ program "--directory-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()) diff --git a/packages/create-medusa-app/src/utils/nextjs-utils.ts b/packages/create-medusa-app/src/utils/nextjs-utils.ts new file mode 100644 index 0000000000..0682d754b9 --- /dev/null +++ b/packages/create-medusa-app/src/utils/nextjs-utils.ts @@ -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 { + 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 { + 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 + } +} diff --git a/packages/create-medusa-app/src/utils/prepare-project.ts b/packages/create-medusa-app/src/utils/prepare-project.ts index 40335933d5..127b5a2403 100644 --- a/packages/create-medusa-app/src/utils/prepare-project.ts +++ b/packages/create-medusa-app/src/utils/prepare-project.ts @@ -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({ diff --git a/packages/medusa-cli/src/commands/new.ts b/packages/medusa-cli/src/commands/new.ts index bab3f26b90..668136aa75 100644 --- a/packages/medusa-cli/src/commands/new.ts +++ b/packages/medusa-cli/src/commands/new.ts @@ -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)