Fixes: FRMW-2728, FRMW-2729 After this PR gets merged the following middleware will be exported from the `@medusajs/framework/http` import path. - applyParamsAsFilters - clearFiltersByKey - applyDefaultFilters - setContext - getQueryConfig - httpCompression - maybeApplyLinkFilter - refetchEntities - unlessPath - validateBody - validateQuery Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
332 lines
8.3 KiB
TypeScript
332 lines
8.3 KiB
TypeScript
import inquirer from "inquirer"
|
|
import slugifyType from "slugify"
|
|
import chalk from "chalk"
|
|
import { getDbClientAndCredentials, runCreateDb } from "../utils/create-db.js"
|
|
import prepareProject from "../utils/prepare-project.js"
|
|
import startMedusa from "../utils/start-medusa.js"
|
|
import open from "open"
|
|
import waitOn from "wait-on"
|
|
import ora, { Ora } from "ora"
|
|
import fs from "fs"
|
|
import path from "path"
|
|
import logMessage from "../utils/log-message.js"
|
|
import createAbortController, {
|
|
isAbortError,
|
|
} from "../utils/create-abort-controller.js"
|
|
import { track } from "medusa-telemetry"
|
|
import boxen from "boxen"
|
|
import { emojify } from "node-emoji"
|
|
import ProcessManager from "../utils/process-manager.js"
|
|
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"
|
|
import {
|
|
getNodeVersion,
|
|
MIN_SUPPORTED_NODE_VERSION,
|
|
} from "../utils/node-version.js"
|
|
|
|
const slugify = slugifyType.default
|
|
|
|
export type CreateOptions = {
|
|
repoUrl?: string
|
|
seed?: boolean
|
|
skipDb?: boolean
|
|
dbUrl?: string
|
|
browser?: boolean
|
|
migrations?: boolean
|
|
directoryPath?: string
|
|
withNextjsStarter?: boolean
|
|
verbose?: boolean
|
|
}
|
|
|
|
export default async ({
|
|
repoUrl = "",
|
|
seed,
|
|
skipDb,
|
|
dbUrl,
|
|
browser,
|
|
migrations,
|
|
directoryPath,
|
|
withNextjsStarter = false,
|
|
verbose = false,
|
|
}: CreateOptions) => {
|
|
const nodeVersion = getNodeVersion()
|
|
if (nodeVersion < MIN_SUPPORTED_NODE_VERSION) {
|
|
logMessage({
|
|
message: `Medusa requires at least v20 of Node.js. You're using v${nodeVersion}. Please install at least v20 and try again: https://nodejs.org/en/download`,
|
|
type: "error",
|
|
})
|
|
}
|
|
track("CREATE_CLI_CMA")
|
|
|
|
const spinner: Ora = ora()
|
|
const processManager = new ProcessManager()
|
|
const abortController = createAbortController(processManager)
|
|
const factBoxOptions: FactBoxOptions = {
|
|
interval: null,
|
|
spinner,
|
|
processManager,
|
|
message: "",
|
|
title: "",
|
|
verbose,
|
|
}
|
|
let isProjectCreated = false
|
|
let isDbInitialized = false
|
|
let printedMessage = false
|
|
let nextjsDirectory = ""
|
|
|
|
processManager.onTerminated(async () => {
|
|
spinner.stop()
|
|
// prevent an error from occurring if
|
|
// client hasn't been declared yet
|
|
if (isDbInitialized && client) {
|
|
await client.end()
|
|
}
|
|
|
|
// the SIGINT event is triggered twice once the backend runs
|
|
// this ensures that the message isn't printed twice to the user
|
|
if (!printedMessage && isProjectCreated) {
|
|
printedMessage = true
|
|
showSuccessMessage(projectName, undefined, nextjsDirectory)
|
|
}
|
|
|
|
return
|
|
})
|
|
|
|
const projectName = await askForProjectName(directoryPath)
|
|
const projectPath = getProjectPath(projectName, directoryPath)
|
|
const installNextjs = withNextjsStarter || (await askForNextjsStarter())
|
|
|
|
let dbName = !skipDb && !dbUrl ? `medusa-${slugify(projectName)}` : ""
|
|
|
|
let { client, dbConnectionString, ...rest } = !skipDb
|
|
? await getDbClientAndCredentials({
|
|
dbName,
|
|
dbUrl,
|
|
verbose,
|
|
})
|
|
: { client: null, dbConnectionString: "" }
|
|
if ("dbName" in rest) {
|
|
dbName = rest.dbName as string
|
|
}
|
|
isDbInitialized = true
|
|
|
|
track("CMA_OPTIONS", {
|
|
repoUrl,
|
|
seed,
|
|
skipDb,
|
|
browser,
|
|
migrations,
|
|
installNextjs,
|
|
verbose,
|
|
})
|
|
|
|
logMessage({
|
|
message: `${emojify(
|
|
":rocket:"
|
|
)} Starting project setup, this may take a few minutes.`,
|
|
})
|
|
|
|
spinner.start()
|
|
|
|
factBoxOptions.interval = displayFactBox({
|
|
...factBoxOptions,
|
|
title: "Setting up project...",
|
|
})
|
|
|
|
try {
|
|
await runCloneRepo({
|
|
projectName: projectPath,
|
|
repoUrl,
|
|
abortController,
|
|
spinner,
|
|
verbose,
|
|
})
|
|
} catch {
|
|
return
|
|
}
|
|
|
|
factBoxOptions.interval = displayFactBox({
|
|
...factBoxOptions,
|
|
message: "Created project directory",
|
|
})
|
|
|
|
nextjsDirectory = installNextjs
|
|
? await installNextjsStarter({
|
|
directoryName: projectPath,
|
|
abortController,
|
|
factBoxOptions,
|
|
verbose,
|
|
processManager,
|
|
})
|
|
: ""
|
|
|
|
if (client && !dbUrl) {
|
|
factBoxOptions.interval = displayFactBox({
|
|
...factBoxOptions,
|
|
title: "Creating database...",
|
|
})
|
|
client = await runCreateDb({ client, dbName, spinner })
|
|
|
|
factBoxOptions.interval = displayFactBox({
|
|
...factBoxOptions,
|
|
message: `Database ${dbName} created`,
|
|
})
|
|
}
|
|
|
|
// prepare project
|
|
let inviteToken: string | undefined = undefined
|
|
try {
|
|
inviteToken = await prepareProject({
|
|
directory: projectPath,
|
|
dbName,
|
|
dbConnectionString,
|
|
seed,
|
|
spinner,
|
|
processManager,
|
|
abortController,
|
|
skipDb,
|
|
migrations,
|
|
onboardingType: installNextjs ? "nextjs" : "default",
|
|
nextjsDirectory,
|
|
client,
|
|
verbose,
|
|
})
|
|
} catch (e: any) {
|
|
if (isAbortError(e)) {
|
|
process.exit()
|
|
}
|
|
|
|
spinner.stop()
|
|
logMessage({
|
|
message: `An error occurred while preparing project: ${e}`,
|
|
type: "error",
|
|
})
|
|
|
|
return
|
|
} finally {
|
|
// close db connection
|
|
await client?.end()
|
|
}
|
|
|
|
spinner.succeed(chalk.green("Project Prepared"))
|
|
|
|
if (skipDb || !browser) {
|
|
showSuccessMessage(projectPath, inviteToken, nextjsDirectory)
|
|
process.exit()
|
|
}
|
|
|
|
// start backend
|
|
logMessage({
|
|
message: "Starting Medusa...",
|
|
})
|
|
|
|
try {
|
|
startMedusa({
|
|
directory: projectPath,
|
|
abortController,
|
|
})
|
|
|
|
if (installNextjs && nextjsDirectory) {
|
|
startNextjsStarter({
|
|
directory: nextjsDirectory,
|
|
abortController,
|
|
verbose,
|
|
})
|
|
}
|
|
} catch (e) {
|
|
if (isAbortError(e)) {
|
|
process.exit()
|
|
}
|
|
|
|
logMessage({
|
|
message: `An error occurred while starting Medusa`,
|
|
type: "error",
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
isProjectCreated = true
|
|
|
|
await waitOn({
|
|
resources: ["http://localhost:9000/health"],
|
|
}).then(async () => {
|
|
open(
|
|
inviteToken
|
|
? `http://localhost:9000/app/invite?token=${inviteToken}&first_run=true`
|
|
: "http://localhost:9000/app"
|
|
)
|
|
})
|
|
}
|
|
|
|
async function askForProjectName(directoryPath?: string): Promise<string> {
|
|
const { projectName } = await inquirer.prompt([
|
|
{
|
|
type: "input",
|
|
name: "projectName",
|
|
message: "What's the name of your project?",
|
|
default: "my-medusa-store",
|
|
filter: (input) => {
|
|
return slugify(input).toLowerCase()
|
|
},
|
|
validate: (input) => {
|
|
if (!input.length) {
|
|
return "Please enter a project name"
|
|
}
|
|
const projectPath = getProjectPath(input, directoryPath)
|
|
return fs.existsSync(projectPath) &&
|
|
fs.lstatSync(projectPath).isDirectory()
|
|
? "A directory already exists with the same name. Please enter a different project name."
|
|
: true
|
|
},
|
|
},
|
|
])
|
|
return projectName
|
|
}
|
|
|
|
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}yarn dev${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",
|
|
textAlignment: "center",
|
|
padding: 1,
|
|
margin: 1,
|
|
float: "center",
|
|
}
|
|
),
|
|
})
|
|
}
|
|
|
|
function getProjectPath(projectName: string, directoryPath?: string) {
|
|
return path.join(directoryPath || "", projectName)
|
|
}
|
|
|
|
function getInviteUrl(inviteToken: string) {
|
|
return `http://localhost:7001/invite?token=${inviteToken}&first_run=true`
|
|
}
|