chore(): start moving some packages to the core directory (#7215)

This commit is contained in:
Adrien de Peretti authored and GitHub committed 2024-05-03 13:37:41 +02:00
1 parent fdee748eed
commit bbccd6481d
1436 files changed
+275 -756

No files matched your search

@@ -0,0 +1,4 @@
node_modules
/dist
yarn.lock
my-medusa-store
+188
View File
@@ -0,0 +1,188 @@
# Change Log
## 1.2.8
### Patch Changes
- [#6027](https://github.com/medusajs/medusa/pull/6027) [`fe1d3a4a78`](https://github.com/medusajs/medusa/commit/fe1d3a4a78385bb356539d6f9cba5ce2012f5da8) Thanks [@shahednasser](https://github.com/shahednasser)! - feat(create-medusa-app): Add a `--verbose` option.
## 1.2.7
### Patch Changes
- [#6729](https://github.com/medusajs/medusa/pull/6729) [`fbc369705d`](https://github.com/medusajs/medusa/commit/fbc369705d4feae21d77ea2fc59173ac9519cee6) Thanks [@shahednasser](https://github.com/shahednasser)! - feat(create-medusa-app): Add `--v2` option
## 1.2.6
### Patch Changes
- [#5983](https://github.com/medusajs/medusa/pull/5983) [`f86877586`](https://github.com/medusajs/medusa/commit/f86877586147ecedbf7f56a1c57f37ef0c33286c) Thanks [@kasperkristensen](https://github.com/kasperkristensen)! - fix(create-medusa-app,medusa-core-utils): Use NodeJS.Timeout instead of NodeJS.Timer as the latter was deprecated in v14.
chore(icons): Update icons to latest version.
## 1.2.5
### Patch Changes
- [#5547](https://github.com/medusajs/medusa/pull/5547) [`09ab1d1be`](https://github.com/medusajs/medusa/commit/09ab1d1be6ec0c50e3fcc828c740265debd2222b) Thanks [@egormkn](https://github.com/egormkn)! - feat(create-medusa-app): print error message for failed database connection
## 1.2.4
### Patch Changes
- [#5474](https://github.com/medusajs/medusa/pull/5474) [`03959c3e3`](https://github.com/medusajs/medusa/commit/03959c3e3a38c155607647e70668ee250b76fda9) Thanks [@shahednasser](https://github.com/shahednasser)! - feat(create-medusa-app): improve spinner style
## 1.2.3
### Patch Changes
- [#5404](https://github.com/medusajs/medusa/pull/5404) [`a1807aea8`](https://github.com/medusajs/medusa/commit/a1807aea83a5bf82c4221839a1fa57b41d2cc6ac) Thanks [@shahednasser](https://github.com/shahednasser)! - feat(create-medusa-app): add tracking for selected options
## 1.2.2
### Patch Changes
- [#5189](https://github.com/medusajs/medusa/pull/5189) [`18a05dee8`](https://github.com/medusajs/medusa/commit/18a05dee86f55366c92a2669eacbccf526373ff5) Thanks [@shahednasser](https://github.com/shahednasser)! - fix(create-medusa-app): fix inconsistency in checking errors in migrations
## 1.2.1
### Patch Changes
- [#5061](https://github.com/medusajs/medusa/pull/5061) [`5b09f816c`](https://github.com/medusajs/medusa/commit/5b09f816cbb44d457c8cbdee803d9f84bd3fed0d) Thanks [@shahednasser](https://github.com/shahednasser)! - fix(create-medusa-app): fix command for windows OS
## 1.2.0
### Minor Changes
- [#4968](https://github.com/medusajs/medusa/pull/4968) [`240b03800`](https://github.com/medusajs/medusa/commit/240b038006924d4872de068c98e2d6862145cb52) Thanks [@shahednasser](https://github.com/shahednasser)! - feat(create-medusa-app): add install Next.js storefront option
## 1.1.1
### Patch Changes
- Updated dependencies [[`c58588904`](https://github.com/medusajs/medusa/commit/c58588904c5631111603b15afacf7cdc4c738cc4)]:
- medusa-telemetry@0.0.17
## 1.1.0
### Minor Changes
- [#4794](https://github.com/medusajs/medusa/pull/4794) [`c684d16ec`](https://github.com/medusajs/medusa/commit/c684d16ec012a547a9981480db8b4b96f5f22904) Thanks [@shahednasser](https://github.com/shahednasser)! - feat(create-medusa-app): remove `--stable` option and change to clone default branch
### Patch Changes
- [#4733](https://github.com/medusajs/medusa/pull/4733) [`30ce35b16`](https://github.com/medusajs/medusa/commit/30ce35b163afa25f4e1d8d1bd392f401a3b413df) Thanks [@shahednasser](https://github.com/shahednasser)! - feat(create-medusa-app, utils, medusa-cli): add database options + remove util from `@medusajs/utils`
## 1.0.4
### Patch Changes
- [#4567](https://github.com/medusajs/medusa/pull/4567) [`15e87a810`](https://github.com/medusajs/medusa/commit/15e87a8100c9fe66f6d120423ef0351f4c657e7e) Thanks [@shahednasser](https://github.com/shahednasser)! - feat(create-medusa-app): add `stable` option + add URI encoding to database string
- Updated dependencies [[`131477faf`](https://github.com/medusajs/medusa/commit/131477faf0409c49d4aacf26ea591e33b2fa22fd)]:
- @medusajs/utils@1.9.3
## 1.0.3
### Patch Changes
- [#4543](https://github.com/medusajs/medusa/pull/4543) [`f32588122`](https://github.com/medusajs/medusa/commit/f32588122733a4dca54d06a53d18e14ad8ed021b) Thanks [@shahednasser](https://github.com/shahednasser)! - fix(create-medusa-app): improved error messages
## 1.0.2
### Patch Changes
- [#4493](https://github.com/medusajs/medusa/pull/4493) [`4b4296dc1`](https://github.com/medusajs/medusa/commit/4b4296dc1651d325bd7e64eb0fdb0584989828f7) Thanks [@shahednasser](https://github.com/shahednasser)! - feat(create-medusa-app): remove .git directory in the project
- [#4369](https://github.com/medusajs/medusa/pull/4369) [`d363da2b7`](https://github.com/medusajs/medusa/commit/d363da2b72820defe90b12b0dc741087f3b21090) Thanks [@adrien2p](https://github.com/adrien2p)! - chore(create-medusa-app): Cleanup the main script for readability and maintanability
- Updated dependencies [[`499c3478c`](https://github.com/medusajs/medusa/commit/499c3478c910c8b922a15cc6f4d9fbad122a347f), [`9dcdc0041`](https://github.com/medusajs/medusa/commit/9dcdc0041a2b08cc0723343dd8d9127d9977b086), [`9760d4a96`](https://github.com/medusajs/medusa/commit/9760d4a96c27f6f89a8c3f3b6e73b17547f97f2a)]:
- @medusajs/utils@1.9.2
## 1.0.1
### Patch Changes
- [#4339](https://github.com/medusajs/medusa/pull/4339) [`6d5da9166`](https://github.com/medusajs/medusa/commit/6d5da9166f609017f92c8c4c34c8eeca53699e3d) Thanks [@shahednasser](https://github.com/shahednasser)! - feat(create-medusa-app): support admin onboarding experience
## 1.0.0
### Major Changes
- [`b779040ac`](https://github.com/medusajs/medusa/commit/b779040acbaded8596db697a9f8dbe7451cfb9ca) Thanks [@olivermrbl](https://github.com/olivermrbl)! - chore: Bump create-medusa-app to major
### Minor Changes
- [#4215](https://github.com/medusajs/medusa/pull/4215) [`c04d93cd0`](https://github.com/medusajs/medusa/commit/c04d93cd041a0080af46c6068d6cf4724600af02) Thanks [@shahednasser](https://github.com/shahednasser)! - feat(create-medusa-app): update command for a better onboarding experience
### Patch Changes
- [#4273](https://github.com/medusajs/medusa/pull/4273) [`f98ba5bde`](https://github.com/medusajs/medusa/commit/f98ba5bde83ba785eead31b0c9eb9f135d664178) Thanks [@shahednasser](https://github.com/shahednasser)! - feat(create-medusa-app,medusa-cli): Allow clearing project
- [#4321](https://github.com/medusajs/medusa/pull/4321) [`5ad734740`](https://github.com/medusajs/medusa/commit/5ad7347408746fd717354c99a530aaab5e86ffa2) Thanks [@shahednasser](https://github.com/shahednasser)! - fix(create-medusa-app): disable opening browser
- [#4328](https://github.com/medusajs/medusa/pull/4328) [`e1f43fd20`](https://github.com/medusajs/medusa/commit/e1f43fd207621deec885c06f704c2e4c5d7c559b) Thanks [@shahednasser](https://github.com/shahednasser)! - chore(create-medusa-app): change boxen version
- [#4247](https://github.com/medusajs/medusa/pull/4247) [`4b5b7b514`](https://github.com/medusajs/medusa/commit/4b5b7b51483bae8996235a37e75c0671f9e2994f) Thanks [@shahednasser](https://github.com/shahednasser)! - fix(create-medusa-app): remove seed command from create-medusa-app and improve success message
- Updated dependencies [[`f98ba5bde`](https://github.com/medusajs/medusa/commit/f98ba5bde83ba785eead31b0c9eb9f135d664178), [`14c0f62f8`](https://github.com/medusajs/medusa/commit/14c0f62f84704a4c87beff3daaff60a52f5c88b8)]:
- @medusajs/utils@1.9.1
## 0.0.10
### Patch Changes
- [#3376](https://github.com/medusajs/medusa/pull/3376) [`88392e017`](https://github.com/medusajs/medusa/commit/88392e017653b53069793bd2ee451c2e6b7bcf17) Thanks [@olivermrbl](https://github.com/olivermrbl)! - chore(create-medusa-app): Remove admin + Gatsby starter from npx
## 0.0.10-rc.0
### Patch Changes
- [#3376](https://github.com/medusajs/medusa/pull/3376) [`88392e017`](https://github.com/medusajs/medusa/commit/88392e017653b53069793bd2ee451c2e6b7bcf17) Thanks [@olivermrbl](https://github.com/olivermrbl)! - chore(create-medusa-app): Remove admin + Gatsby starter from npx
## 0.0.9
### Patch Changes
- [#3217](https://github.com/medusajs/medusa/pull/3217) [`8c5219a31`](https://github.com/medusajs/medusa/commit/8c5219a31ef76ee571fbce84d7d57a63abe56eb0) Thanks [@adrien2p](https://github.com/adrien2p)! - chore: Fix npm packages files included
## 0.0.8
### Patch Changes
- [#3185](https://github.com/medusajs/medusa/pull/3185) [`08324355a`](https://github.com/medusajs/medusa/commit/08324355a4466b017a0bc7ab1d333ee3cd27b8c4) Thanks [@olivermrbl](https://github.com/olivermrbl)! - chore: Patches all dependencies + minor bumps `winston` to include a [fix for a significant memory leak](https://github.com/winstonjs/winston/pull/2057)
## 0.0.7
### Patch Changes
- [#2069](https://github.com/medusajs/medusa/pull/2069) [`ad717b953`](https://github.com/medusajs/medusa/commit/ad717b9533a0500e20c4e312d1ee48b35ea9d5e1) Thanks [@olivermrbl](https://github.com/olivermrbl)! - Remove deprecated dependency `@hapi/joi`
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.0.6](https://github.com/medusajs/medusa/compare/create-medusa-app@0.0.5...create-medusa-app@0.0.6) (2022-01-11)
### Features
- **create-medusa-app:** add medusa.express ([#981](https://github.com/medusajs/medusa/issues/981)) ([bbc16d6](https://github.com/medusajs/medusa/commit/bbc16d6b115fb389ee0fe58d909e74a162686163))
## [0.0.5](https://github.com/medusajs/medusa/compare/create-medusa-app@0.0.3...create-medusa-app@0.0.5) (2021-10-18)
**Note:** Version bump only for package create-medusa-app
## [0.0.4](https://github.com/medusajs/medusa/compare/create-medusa-app@0.0.3...create-medusa-app@0.0.4) (2021-10-18)
**Note:** Version bump only for package create-medusa-app
## [0.0.3](https://github.com/medusajs/medusa/compare/create-medusa-app@0.0.1...create-medusa-app@0.0.3) (2021-09-15)
### Bug Fixes
- flip seed flag to default to true ([#398](https://github.com/medusajs/medusa/issues/398)) ([dd1025f](https://github.com/medusajs/medusa/commit/dd1025fd5369eb264d7d5f5d6db41c888259d786))
- versioning ([3b8901e](https://github.com/medusajs/medusa/commit/3b8901ebc2fb41dc8d5372a808c5eaafd7d32646))
## 0.0.1 (2021-09-14)
### Features
- adds create-medusa-app ([#377](https://github.com/medusajs/medusa/issues/377)) ([ec6d16e](https://github.com/medusajs/medusa/commit/ec6d16e945f4b8a99e9dcc8ae2e92a2318fbc709))
+58
View File
@@ -0,0 +1,58 @@
<p align="center">
<a href="https://www.medusajs.com">
<img alt="Medusa" src="https://user-images.githubusercontent.com/7554214/153162406-bf8fd16f-aa98-4604-b87b-e13ab4baf604.png" width="100" />
</a>
</p>
<h1 align="center">
create-medusa-app
</h1>
<h4 align="center">
<a href="https://docs.medusajs.com">Documentation</a> |
<a href="https://www.medusajs.com">Website</a>
</h4>
<p align="center">
An open source composable commerce engine built for developers.
</p>
<p align="center">
<a href="https://github.com/medusajs/medusa/blob/master/LICENSE">
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="Medusa is released under the MIT license." />
</a>
<a href="https://circleci.com/gh/medusajs/medusa">
<img src="https://circleci.com/gh/medusajs/medusa.svg?style=shield" alt="Current CircleCI build status." />
</a>
<a href="https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md">
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" alt="PRs welcome!" />
</a>
<a href="https://www.producthunt.com/posts/medusa"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Day-%23DA552E" alt="Product Hunt"></a>
<a href="https://discord.gg/xpCwq3Kfn8">
<img src="https://img.shields.io/badge/chat-on%20discord-7289DA.svg" alt="Discord Chat" />
</a>
<a href="https://twitter.com/intent/follow?screen_name=medusajs">
<img src="https://img.shields.io/twitter/follow/medusajs.svg?label=Follow%20@medusajs" alt="Follow @medusajs" />
</a>
</p>
## Overview
Using this NPX command, you can setup a Medusa backend and admin along with a PostgreSQL database in simple steps.
---
## Usage
Run the following command in your terminal:
```bash
npx create-medusa-app@latest
```
Then, answer the prompted questions to setup your PostgreSQL database and Medusa project. Once the setup is done, the Medusa admin dashboard will open in your default browser.
### Options
| Option | Description | Default value |
|--------------------|-------------------------------------------------------|------------------------------------------------------|
| `--repo-url <url>` | Create Medusa project from a different repository URL | `https://github.com/medusajs/medusa-starter-default` |
| `--seed` | Using this option seeds the database with demo data | false |
@@ -0,0 +1,68 @@
{
"name": "create-medusa-app",
"version": "1.2.8",
"description": "Create a Medusa project using a single command.",
"type": "module",
"exports": "./dist/index.js",
"bin": "dist/index.js",
"license": "MIT",
"scripts": {
"dev": "ts-node --esm src/index.ts",
"build": "tsc",
"watch": "tsc --watch",
"prepublishOnly": "cross-env NODE_ENV=production tsc --build"
},
"dependencies": {
"boxen": "^5",
"chalk": "^5.2.0",
"commander": "^10.0.1",
"glob": "^7.1.6",
"inquirer": "^9.2.2",
"medusa-telemetry": "^0.0.17",
"nanoid": "^4.0.2",
"node-emoji": "^2.0.2",
"node-fetch": "^3.3.1",
"open": "^9.1.0",
"ora": "^6.3.0",
"pg": "^8.10.0",
"slugify": "^1.6.6",
"uuid": "^9.0.0",
"validator": "^13.9.0",
"wait-on": "^7.0.1",
"winston": "^3.9.0"
},
"devDependencies": {
"@types/chalk": "^2.2.0",
"@types/commander": "^2.12.2",
"@types/configstore": "^6.0.0",
"@types/inquirer": "^9.0.3",
"@types/node-emoji": "^1.8.2",
"@types/pg": "^8.6.6",
"@types/uuid": "^9.0.1",
"@types/validator": "^13.7.17",
"@types/wait-on": "^5.3.1",
"@typescript-eslint/eslint-plugin": "^6.19.0",
"@typescript-eslint/parser": "^6.19.0",
"configstore": "^6.0.0",
"eslint": "^8.40.0",
"eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"prettier": "^2.8.8",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
},
"engines": {
"node": ">=14.16"
},
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa.git",
"directory": "packages/create-medusa-app"
},
"author": "Medusa",
"publishConfig": {
"access": "public"
},
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
@@ -0,0 +1,342 @@
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 isEmailImported from "validator/lib/isEmail.js"
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 { 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
export type CreateOptions = {
repoUrl?: string
seed?: boolean
// commander passed --no-boilerplate as boilerplate
boilerplate?: boolean
skipDb?: boolean
dbUrl?: string
browser?: boolean
migrations?: boolean
directoryPath?: string
withNextjsStarter?: boolean
verbose?: boolean
v2?: boolean
}
export default async ({
repoUrl = "",
seed,
boilerplate,
skipDb,
dbUrl,
browser,
migrations,
directoryPath,
withNextjsStarter = false,
verbose = false,
v2 = false,
}: CreateOptions) => {
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,
}
const dbName = !skipDb && !dbUrl ? `medusa-${nanoid(4)}` : ""
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 adminEmail =
!skipDb && migrations ? await askForAdminEmail(seed, boilerplate) : ""
const installNextjs = withNextjsStarter || (await askForNextjsStarter())
let { client, dbConnectionString } = !skipDb
? await getDbClientAndCredentials({
dbName,
dbUrl,
verbose,
})
: { client: null, dbConnectionString: "" }
isDbInitialized = true
track("CMA_OPTIONS", {
repoUrl,
seed,
boilerplate,
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,
v2,
})
} catch {
return
}
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
message: "Created project directory",
})
nextjsDirectory = installNextjs
? await installNextjsStarter({
directoryName: projectPath,
abortController,
factBoxOptions,
verbose,
})
: ""
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,
dbConnectionString,
admin: {
email: adminEmail,
},
seed,
boilerplate,
spinner,
processManager,
abortController,
skipDb,
migrations,
onboardingType: installNextjs ? "nextjs" : "default",
nextjsDirectory,
client,
verbose,
v2,
})
} 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:7001/invite?token=${inviteToken}&first_run=true`
: "http://localhost:7001"
)
)
}
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
}
async function askForAdminEmail(
seed?: boolean,
boilerplate?: boolean
): Promise<string> {
const { adminEmail } = await inquirer.prompt([
{
type: "input",
name: "adminEmail",
message: "Enter an email for your admin dashboard user",
default: !seed && boilerplate ? "admin@medusa-test.com" : undefined,
validate: (input) => {
return typeof input === "string" && input.length > 0 && isEmail(input)
? true
: "Please enter a valid email"
},
},
])
return adminEmail
}
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 ? `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`
}
@@ -0,0 +1,53 @@
#!/usr/bin/env node
import { program } from "commander"
import create from "./commands/create.js"
program
.description("Create a new Medusa project")
.option("--repo-url <url>", "URL of repository to use to setup project.")
.option("--seed", "Seed the created database with demo data.")
.option(
"--no-boilerplate",
"Install a Medusa project without the boilerplate and demo files."
)
.option(
"--skip-db",
"Skips creating the database, running migrations, and seeding, and subsequently skips opening the browser.",
false
)
.option(
"--db-url <url>",
"Skips database creation and sets the database URL to the provided URL. Throws an error if can't connect to the database. Will still run migrations and open the admin after project creation."
)
.option(
"--no-migrations",
"Skips running migrations, creating admin user, and seeding. If used, it's expected that you pass the --db-url option with a url of a database that has all necessary migrations. Otherwise, unexpected errors will occur.",
true
)
.option(
"--no-browser",
"Disables opening the browser at the end of the project creation and only shows success message.",
true
)
.option(
"--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
)
.option(
"--verbose",
"Show all logs of underlying commands. Useful for debugging.",
false
)
.option(
"--v2",
"Install Medusa with the V2 feature flag enabled. WARNING: Medusa V2 is still in development and shouldn't be used in production.",
false
)
.parse()
void create(program.opts())
+1
View File
@@ -0,0 +1 @@
declare module "medusa-telemetry"
@@ -0,0 +1,25 @@
import fs from "fs"
import glob from "glob"
import path from "path"
export function clearProject(directory: string) {
const adminFiles = glob.sync(path.join(directory, `src`, `admin/**/*`))
const onboardingFiles = glob.sync(
path.join(directory, `src`, `**/onboarding/`)
)
const typeFiles = glob.sync(path.join(directory, `src`, `types`))
const srcFiles = glob.sync(
path.join(directory, `src`, `**/*.{ts,tsx,js,jsx}`)
)
const files = [...adminFiles, ...onboardingFiles, ...typeFiles, ...srcFiles]
files.forEach((file) =>
fs.rmSync(file, {
recursive: true,
force: true,
})
)
// add empty typescript file to avoid build errors
fs.openSync(path.join(directory, "src", "index.ts"), "w")
}
@@ -0,0 +1,82 @@
import execute from "./execute.js"
import { Ora } from "ora"
import { isAbortError } from "./create-abort-controller.js"
import logMessage from "./log-message.js"
import fs from "fs"
import path from "path"
type CloneRepoOptions = {
directoryName?: string
repoUrl?: string
abortController?: AbortController
verbose?: boolean
v2?: boolean
}
const DEFAULT_REPO = "https://github.com/medusajs/medusa-starter-default"
const V2_BRANCH = "feat/v2"
export default async function cloneRepo({
directoryName = "",
repoUrl,
abortController,
verbose = false,
v2 = false,
}: CloneRepoOptions) {
await execute(
[
`git clone ${repoUrl || DEFAULT_REPO}${
v2 ? ` -b ${V2_BRANCH}` : ""
} ${directoryName}`,
{
signal: abortController?.signal,
},
],
{ verbose }
)
}
export async function runCloneRepo({
projectName,
repoUrl,
abortController,
spinner,
verbose = false,
v2 = false,
}: {
projectName: string
repoUrl: string
abortController: AbortController
spinner: Ora
verbose?: boolean
v2?: boolean
}) {
try {
await cloneRepo({
directoryName: projectName,
repoUrl,
abortController,
verbose,
v2,
})
deleteGitDirectory(projectName)
} catch (e) {
if (isAbortError(e)) {
process.exit()
}
spinner.stop()
logMessage({
message: `An error occurred while setting up your project: ${e}`,
type: "error",
})
}
}
function deleteGitDirectory(projectDirectory: string) {
fs.rmSync(path.join(projectDirectory, ".git"), {
recursive: true,
force: true,
})
}
@@ -0,0 +1,16 @@
import ProcessManager from "./process-manager.js"
export default (processManager: ProcessManager) => {
const abortController = new AbortController()
processManager.onTerminated(() => abortController.abort())
return abortController
}
export const isAbortError = (e: any) =>
e !== null && "code" in e && e.code === "ABORT_ERR"
export const getAbortError = () => {
return {
code: "ABORT_ERR",
}
}
@@ -0,0 +1,184 @@
import { EOL } from "os"
import pg from "pg"
import postgresClient from "./postgres-client.js"
import inquirer from "inquirer"
import logMessage from "./log-message.js"
import formatConnectionString from "./format-connection-string.js"
import { Ora } from "ora"
import { getCurrentOs } from "./get-current-os.js"
type CreateDbOptions = {
client: pg.Client
db: string
}
export default async function createDb({ client, db }: CreateDbOptions) {
await client.query(`CREATE DATABASE "${db}"`)
}
export async function runCreateDb({
client,
dbName,
spinner,
}: {
client: pg.Client
dbName: string
spinner: Ora
}): Promise<pg.Client> {
let newClient = client
try {
// create postgres database
await createDb({
client,
db: dbName,
})
// create a new connection with database selected
await client.end()
newClient = await postgresClient({
user: client.user,
password: client.password,
database: dbName,
})
} catch (e) {
spinner.stop()
logMessage({
message: `An error occurred while trying to create your database: ${e}`,
type: "error",
})
}
return newClient
}
async function getForDbName({
dbName,
verbose = false,
}: {
dbName: string
verbose?: boolean
}): Promise<{
client: pg.Client
dbConnectionString: string
}> {
let client!: pg.Client
let postgresUsername = "postgres"
let postgresPassword = ""
try {
client = await postgresClient({
user: postgresUsername,
password: postgresPassword,
})
} catch (e) {
if (verbose) {
logMessage({
message: `The following error occured when connecting to the database: ${e}`,
type: "verbose",
})
}
// ask for the user's postgres credentials
const answers = await inquirer.prompt([
{
type: "input",
name: "postgresUsername",
message: "Enter your Postgres username",
default: "postgres",
validate: (input) => {
return typeof input === "string" && input.length > 0
},
},
{
type: "password",
name: "postgresPassword",
message: "Enter your Postgres password",
},
])
postgresUsername = answers.postgresUsername
postgresPassword = answers.postgresPassword
try {
client = await postgresClient({
user: postgresUsername,
password: postgresPassword,
})
} catch (e) {
logMessage({
message: `Couldn't connect to PostgreSQL because of the following error: ${e}.${EOL}${EOL}Make sure you have PostgreSQL installed and the credentials you provided are correct.${EOL}${EOL}You can learn how to install PostgreSQL here: https://docs.medusajs.com/development/backend/prepare-environment?os=${getCurrentOs()}#postgresql${EOL}${EOL}If you keep running into this issue despite having PostgreSQL installed, please check out our troubleshooting guidelines: https://docs.medusajs.com/troubleshooting/database-error`,
type: "error",
})
}
}
// format connection string
const dbConnectionString = formatConnectionString({
user: postgresUsername,
password: postgresPassword,
host: client!.host,
db: dbName,
})
return {
client,
dbConnectionString,
}
}
async function getForDbUrl({
dbUrl,
verbose = false,
}: {
dbUrl: string
verbose?: boolean
}): Promise<{
client: pg.Client
dbConnectionString: string
}> {
let client!: pg.Client
try {
client = await postgresClient({
connectionString: dbUrl,
})
} catch (e) {
if (verbose) {
logMessage({
message: `The following error occured when connecting to the database: ${e}`,
type: "verbose",
})
}
logMessage({
message: `Couldn't connect to PostgreSQL using the database URL you passed. Make sure it's correct and try again.`,
type: "error",
})
}
return {
client,
dbConnectionString: dbUrl,
}
}
export async function getDbClientAndCredentials({
dbName = "",
dbUrl = "",
verbose = false,
}): Promise<{
client: pg.Client
dbConnectionString: string
verbose?: boolean
}> {
if (dbName) {
return await getForDbName({
dbName,
verbose,
})
} else {
return await getForDbUrl({
dbUrl,
verbose,
})
}
}
@@ -0,0 +1,72 @@
import { exec, spawnSync, SpawnSyncOptions } from "child_process"
import util from "util"
import { getAbortError } from "./create-abort-controller.js"
const promiseExec = util.promisify(exec)
type ExecuteOptions = {
stdout?: string
stderr?: string
}
type VerboseOptions = {
verbose?: boolean
// Since spawn doesn't allow us to both retrieve the
// output and output it live without using events,
// enabling this option, which is only useful if `verbose` is `true`,
// defers the output of the process until after the process is executed
// instead of outputting the log in realtime, which is the default.
// it prioritizes retrieving the output over outputting it in real-time.
needOutput?: boolean
}
type PromiseExecParams = Parameters<typeof promiseExec>
type SpawnParams = [string, SpawnSyncOptions]
const execute = async (
command: SpawnParams | PromiseExecParams,
{ verbose = false, needOutput = false }: VerboseOptions
): Promise<ExecuteOptions> => {
if (verbose) {
const [commandStr, options] = command as SpawnParams
const childProcess = spawnSync(commandStr, {
...options,
shell: true,
stdio: needOutput
? "pipe"
: [process.stdin, process.stdout, process.stderr],
})
if (childProcess.error) {
throw childProcess.error
}
if (
childProcess.signal &&
["SIGINT", "SIGTERM"].includes(childProcess.signal)
) {
console.log("abortingggg")
throw getAbortError()
}
if (needOutput) {
console.log(
childProcess.stdout?.toString() || childProcess.stderr?.toString()
)
}
return {
stdout: childProcess.stdout?.toString() || "",
stderr: childProcess.stderr?.toString() || "",
}
} else {
const childProcess = await promiseExec(...(command as PromiseExecParams))
return {
stdout: childProcess.stdout as string,
stderr: childProcess.stderr as string,
}
}
}
export default execute
@@ -0,0 +1,136 @@
import boxen from "boxen"
import chalk from "chalk"
import { emojify } from "node-emoji"
import { Ora } from "ora"
import ProcessManager from "./process-manager.js"
export type FactBoxOptions = {
interval: NodeJS.Timeout | null
spinner: Ora
processManager: ProcessManager
message?: string
title?: string
verbose?: boolean
}
const facts = [
"Plugins allow you to integrate third-party services for payment, fulfillment, notifications, and more.",
"You can specify a product's availability in one or more sales channels.",
"Payment and shipping options and providers can be configured per region.",
"Tax-inclusive pricing allows you to set prices for products, shipping options, and more without having to worry about calculating taxes.",
"Medusa provides multi-currency and region support, with full control over prices for each currency and region.",
"You can organize customers by customer groups and set special prices for them.",
"You can specify the inventory of products per location and sales channel.",
"Publishable-API Keys allow you to send requests to the backend within a scoped resource.",
"You can create custom endpoints by creating a TypeScript file under the src/api directory.",
"You can listen to events to perform asynchronous actions using Subscribers.",
"An entity represents a table in the database. You can create a table by creating a custom entity and migration.",
"Medusa's store endpoint paths are prefixed by /store. The admin endpoints are prefixed by /admin.",
"Medusa provides a JavaScript client and a React library that you can use to build a storefront or a custom admin.",
"Services are classes with methods related to an entity or functionality. You can create a custom service in a TypeScript file under src/services.",
"Modules allow you to replace an entire functionality with your custom logic.",
"The event bus module is responsible for triggering events and relaying them to subscribers.",
"The cache module is responsible for caching data that requires heavy computation.",
]
export const getFact = () => {
const randIndex = Math.floor(Math.random() * facts.length)
return facts[randIndex]
}
export const showFact = ({
spinner,
title,
verbose,
}: Pick<FactBoxOptions, "spinner" | "verbose"> & {
title: string
}) => {
const fact = getFact()
if (verbose) {
spinner.stopAndPersist({
symbol: chalk.cyan("⠋"),
text: title,
})
} else {
spinner.text = `${title}\n${boxen(`${fact}`, {
title: chalk.cyan(`${emojify(":bulb:")} Medusa Tips`),
titleAlignment: "center",
textAlignment: "center",
padding: 1,
margin: 1,
})}`
}
}
export const createFactBox = ({
spinner,
title,
processManager,
verbose,
}: Pick<FactBoxOptions, "spinner" | "processManager" | "verbose"> & {
title: string
}): NodeJS.Timeout => {
showFact({ spinner, title, verbose })
const interval = setInterval(() => {
showFact({ spinner, title, verbose })
}, 10000)
processManager.addInterval(interval)
return interval
}
export const resetFactBox = ({
interval,
spinner,
successMessage,
processManager,
newTitle,
verbose,
}: Pick<
FactBoxOptions,
"interval" | "spinner" | "processManager" | "verbose"
> & {
successMessage: string
newTitle?: string
}): NodeJS.Timeout | null => {
if (interval) {
clearInterval(interval)
}
spinner.succeed(chalk.green(successMessage)).start()
let newInterval = null
if (newTitle) {
newInterval = createFactBox({
spinner,
title: newTitle,
processManager,
verbose,
})
}
return newInterval
}
export function displayFactBox({
interval,
spinner,
processManager,
title = "",
message = "",
verbose = false,
}: FactBoxOptions): NodeJS.Timeout | null {
if (!message) {
return createFactBox({ spinner, title, processManager, verbose })
}
return resetFactBox({
interval,
spinner,
successMessage: message,
processManager,
newTitle: title,
verbose,
})
}
@@ -0,0 +1,29 @@
type ConnectionStringOptions = {
user?: string
password?: string
host?: string
db: string
}
export function encodeDbValue(value: string): string {
return encodeURIComponent(value)
}
export default ({ user, password, host, db }: ConnectionStringOptions) => {
let connection = `postgres://`
if (user) {
connection += encodeDbValue(user)
}
if (password) {
connection += `:${encodeDbValue(password)}`
}
if (user || password) {
connection += "@"
}
connection += `${host}/${db}`
return connection
}
@@ -0,0 +1,17 @@
import Configstore from "configstore"
let config: Configstore
export const getConfigStore = (): Configstore => {
if (!config) {
config = new Configstore(
`medusa`,
{},
{
globalConfigPath: true,
}
)
}
return config
}
@@ -0,0 +1,10 @@
export const getCurrentOs = (): string => {
switch (process.platform) {
case "darwin":
return "macos"
case "linux":
return "linux"
default:
return "windows"
}
}
@@ -0,0 +1,27 @@
import chalk from "chalk"
import { program } from "commander"
import { logger } from "./logger.js"
type LogOptions = {
message: string
type?: "error" | "success" | "info" | "warning" | "verbose"
}
export default ({ message, type = "info" }: LogOptions) => {
switch (type) {
case "info":
logger.info(chalk.white(message))
break
case "success":
logger.info(chalk.green(message))
break
case "warning":
logger.warning(chalk.yellow(message))
break
case "verbose":
logger.info(`${chalk.bgYellowBright("VERBOSE LOG:")} ${message}`)
break
case "error":
program.error(chalk.bold.red(message))
}
}
@@ -0,0 +1,10 @@
import winston from "winston"
const consoleTransport = new winston.transports.Console({
format: winston.format.printf((log) => log.message),
})
const options = {
transports: [consoleTransport],
}
export const logger = winston.createLogger(options)
@@ -0,0 +1,116 @@
import inquirer from "inquirer"
import { exec } from "child_process"
import execute from "./execute.js"
import { FactBoxOptions, displayFactBox } from "./facts.js"
import fs from "fs"
import path from "path"
import { customAlphabet } from "nanoid"
import { isAbortError } from "./create-abort-controller.js"
import logMessage from "./log-message.js"
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
verbose?: boolean
}
export async function installNextjsStarter({
directoryName,
abortController,
factBoxOptions,
verbose = false,
}: 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 execute(
[
`npx create-next-app -e ${NEXTJS_REPO} ${nextjsDirectory}`,
{
signal: abortController?.signal,
env: {
...process.env,
npm_config_yes: "yes",
},
},
],
{ verbose }
)
} catch (e) {
if (isAbortError(e)) {
process.exit()
}
logMessage({
message: `An error occurred while installing Next.js storefront: ${e}`,
type: "error",
})
}
fs.renameSync(
path.join(nextjsDirectory, ".env.template"),
path.join(nextjsDirectory, ".env.local")
)
displayFactBox({
...factBoxOptions,
message: `Installed Next.js Starter successfully in the ${nextjsDirectory} directory.`,
})
return nextjsDirectory
}
type StartOptions = {
directory: string
abortController?: AbortController
verbose?: boolean
}
export function startNextjsStarter({
directory,
abortController,
verbose = false,
}: StartOptions) {
const childProcess = exec(`npm run dev`, {
cwd: directory,
signal: abortController?.signal,
})
if (verbose) {
childProcess.stdout?.pipe(process.stdout)
childProcess.stderr?.pipe(process.stderr)
}
}
@@ -0,0 +1,17 @@
import pg from "pg"
const { Client } = pg
type PostgresConnection = {
user?: string
password?: string
connectionString?: string
database?: string
}
export default async (connect: PostgresConnection) => {
const client = new Client(connect)
await client.connect()
return client
}
@@ -0,0 +1,292 @@
import chalk from "chalk"
import fs from "fs"
import path from "path"
import { Ora } from "ora"
import execute from "./execute.js"
import { EOL } from "os"
import { displayFactBox, FactBoxOptions } from "./facts.js"
import ProcessManager from "./process-manager.js"
import { clearProject } from "./clear-project.js"
import type { Client } from "pg"
type PrepareOptions = {
directory: string
dbConnectionString: string
admin?: {
email: string
}
seed?: boolean
boilerplate?: boolean
spinner: Ora
processManager: ProcessManager
abortController?: AbortController
skipDb?: boolean
migrations?: boolean
onboardingType?: "default" | "nextjs"
nextjsDirectory?: string
client: Client | null
verbose?: boolean
v2?: boolean
}
export default async ({
directory,
dbConnectionString,
admin,
seed,
boilerplate,
spinner,
processManager,
abortController,
skipDb,
migrations,
onboardingType = "default",
nextjsDirectory = "",
client,
verbose = false,
v2 = false,
}: PrepareOptions) => {
// initialize execution options
const execOptions = {
cwd: directory,
signal: abortController?.signal,
}
const npxOptions = {
...execOptions,
env: {
...process.env,
npm_config_yes: "yes",
},
}
const factBoxOptions: FactBoxOptions = {
interval: null,
spinner,
processManager,
message: "",
title: "",
verbose,
}
// initialize the invite token to return
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 (v2) {
env += `${EOL}POSTGRES_URL=${dbConnectionString}`
}
if (nextjsDirectory) {
env += `${EOL}MEDUSA_ADMIN_ONBOARDING_NEXTJS_DIRECTORY=${nextjsDirectory}`
}
// add connection string to project
fs.appendFileSync(path.join(directory, `.env`), env)
}
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
spinner,
title: "Installing dependencies...",
processManager,
})
await processManager.runProcess({
process: async () => {
try {
await execute([`yarn`, execOptions], { verbose })
} catch (e) {
// yarn isn't available
// use npm
await execute([`npm install --legacy-peer-deps`, execOptions], {
verbose,
})
}
},
ignoreERESOLVE: true,
})
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
message: "Installed Dependencies",
})
if (!boilerplate) {
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
title: "Preparing Project Directory...",
})
// delete files and directories related to onboarding
clearProject(directory)
displayFactBox({
...factBoxOptions,
message: "Prepared Project Directory",
})
}
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
title: "Building Project...",
})
await processManager.runProcess({
process: async () => {
try {
await execute([`yarn build`, execOptions], { verbose })
} catch (e) {
// yarn isn't available
// use npm
await execute([`npm run build`, execOptions], { verbose })
}
},
ignoreERESOLVE: true,
})
displayFactBox({ ...factBoxOptions, message: "Project Built" })
if (!skipDb && migrations) {
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
title: "Running Migrations...",
})
// run migrations
await processManager.runProcess({
process: async () => {
const proc = await execute(
["npx @medusajs/medusa-cli@latest migrations run", npxOptions],
{ verbose, needOutput: true }
)
if (client) {
// check the migrations table is in the database
// to ensure that migrations ran
let errorOccurred = false
try {
const migrations = await client.query(
`SELECT * FROM "${v2 ? "mikro_orm_migrations" : "migrations"}"`
)
errorOccurred = migrations.rowCount == 0
} catch (e) {
// avoid error thrown if the migrations table
// doesn't exist
errorOccurred = true
}
// ensure that migrations actually ran in case of an uncaught error
if (errorOccurred && (proc.stderr || proc.stdout)) {
throw new Error(
`An error occurred while running migrations: ${
proc.stderr || proc.stdout
}`
)
}
}
},
})
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
message: "Ran Migrations",
})
}
if (admin && !skipDb && migrations && !v2) {
// create admin user
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
title: "Creating an admin user...",
})
await processManager.runProcess({
process: async () => {
const proc = await execute(
[
`npx @medusajs/medusa-cli@latest user -e ${admin.email} --invite`,
npxOptions,
],
{ verbose, needOutput: true }
)
// get invite token from stdout
const match = (proc.stdout as string).match(
/Invite token: (?<token>.+)/
)
inviteToken = match?.groups?.token
},
})
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
message: "Created admin user",
})
}
if (!skipDb && migrations) {
if (seed || !boilerplate) {
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
title: "Seeding database...",
})
// check if a seed file exists in the project
if (!fs.existsSync(path.join(directory, "data", "seed.json"))) {
spinner
?.warn(
chalk.yellow(
"Seed file was not found in the project. Skipping seeding..."
)
)
.start()
return inviteToken
}
await processManager.runProcess({
process: async () => {
await execute(
[
`npx @medusajs/medusa-cli@latest seed --seed-file=${path.join(
"data",
"seed.json"
)}`,
npxOptions,
],
{ verbose }
)
},
})
displayFactBox({
...factBoxOptions,
message: "Seeded database with demo data",
})
} else if (
fs.existsSync(path.join(directory, "data", "seed-onboarding.json"))
) {
// seed the database with onboarding seed
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
title: "Finish preparation...",
})
await processManager.runProcess({
process: async () => {
await execute(
[
`npx @medusajs/medusa-cli@latest seed --seed-file=${path.join(
"data",
"seed-onboarding.json"
)}`,
npxOptions,
],
{ verbose }
)
},
})
}
displayFactBox({ ...factBoxOptions, message: "Finished Preparation" })
}
return inviteToken
}
@@ -0,0 +1,60 @@
type ProcessOptions = {
process: Function
ignoreERESOLVE?: boolean
}
export default class ProcessManager {
intervals: NodeJS.Timeout[] = []
static MAX_RETRIES = 3
constructor() {
this.onTerminated(() => {
this.intervals.forEach((interval) => {
clearInterval(interval)
})
})
}
onTerminated(fn: () => Promise<void> | void) {
process.on("SIGTERM", () => fn())
process.on("SIGINT", () => fn())
}
addInterval(interval: NodeJS.Timeout) {
this.intervals.push(interval)
}
// when running commands with npx or npm sometimes they
// terminate with EAGAIN error unexpectedly
// this utility function allows retrying the process if
// EAGAIN occurs, or otherwise throw the error that occurs
async runProcess({ process, ignoreERESOLVE }: ProcessOptions) {
let processError = false
let retries = 0
do {
++retries
try {
await process()
} catch (error) {
if (
typeof error === "object" &&
error !== null &&
"code" in error &&
error?.code === "EAGAIN"
) {
processError = true
} else if (
ignoreERESOLVE &&
typeof error === "object" &&
error !== null &&
"code" in error &&
error?.code === "ERESOLVE"
) {
// ignore error
} else {
throw error
}
}
} while (processError && retries <= ProcessManager.MAX_RETRIES)
}
}
@@ -0,0 +1,21 @@
import { exec } from "child_process"
type StartOptions = {
directory: string
abortController?: AbortController
}
export default ({ directory, abortController }: StartOptions) => {
const childProcess = exec(`npx @medusajs/medusa-cli@latest develop`, {
cwd: directory,
signal: abortController?.signal,
env: {
...process.env,
OPEN_BROWSER: "false",
npm_config_yes: "yes",
},
})
childProcess.stdout?.pipe(process.stdout)
childProcess.stderr?.pipe(process.stderr)
}
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "Node16",
"moduleResolution": "node16",
"outDir": "./dist",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
},
"include": ["src"],
"ts-node": {
"esm": true,
"experimentalSpecifierResolution": "node",
"transpileOnly": true
}
}