fix(create-medusa-app): ensure the same package manager is used consistently (#12714)

* fix(create-medusa-app): ensure the same package manager is used consistently

* fix verbose not working as expected

* improvements

* remove legacy peer deps

* format
This commit is contained in:
Shahed Nasser
2025-06-16 14:07:35 +03:00
committed by GitHub
parent 6dc65201f6
commit 2e861a9449
7 changed files with 115 additions and 47 deletions

View File

@@ -0,0 +1,5 @@
---
"create-medusa-app": patch
---
fix(create-medusa-app): ensure the same package manager is used consistently

View File

@@ -32,13 +32,15 @@ const execute = async (
const childProcess = spawnSync(commandStr, {
...options,
shell: true,
stdio: needOutput
? "pipe"
: [process.stdin, process.stdout, process.stderr],
stdio: needOutput ?
"pipe" :
[process.stdin, process.stdout, process.stderr],
})
if (childProcess.error) {
throw childProcess.error
if (childProcess.error || childProcess.status !== 0) {
throw childProcess.error ||
childProcess.stderr?.toString() ||
`${commandStr} failed with status ${childProcess.status}`
}
if (

View File

@@ -0,0 +1,87 @@
import execute from "./execute.js"
import ProcessManager from "./process-manager.js"
export default class PackageManager {
protected packageManager?: "npm" | "yarn"
protected processManager: ProcessManager
protected verbose
constructor(processManager: ProcessManager, verbose = false) {
this.processManager = processManager
this.verbose = verbose
}
async setPackageManager(execOptions: Record<string, unknown>): Promise<void> {
if (this.packageManager) {
return
}
// check whether yarn is available
await this.processManager.runProcess({
process: async () => {
try {
await execute([`yarn -v`, execOptions], { verbose: this.verbose })
// yarn is available
this.packageManager = "yarn"
} catch (e) {
// yarn isn't available
// use npm
this.packageManager = "npm"
}
},
ignoreERESOLVE: true,
})
}
async installDependencies(
execOptions: Record<string, unknown>,
) {
if (!this.packageManager) {
await this.setPackageManager(execOptions)
}
const command = this.packageManager === "yarn" ?
`yarn` : `npm install`
await this.processManager.runProcess({
process: async () => {
await execute([command, execOptions], {
verbose: this.verbose
})
},
ignoreERESOLVE: true,
})
}
async runCommand(
command: string,
execOptions: Record<string, unknown>,
) {
if (!this.packageManager) {
await this.setPackageManager(execOptions)
}
const commandStr = this.getCommandStr(command)
await this.processManager.runProcess({
process: async () => {
await execute([commandStr, execOptions], {
verbose: this.verbose
})
},
ignoreERESOLVE: true,
})
}
getCommandStr(
command: string,
): string {
if (!this.packageManager) {
throw new Error("Package manager not set")
}
return this.packageManager === "yarn"
? `yarn ${command}`
: `npm run ${command}`
}
}

View File

@@ -6,6 +6,7 @@ import { EOL } from "os"
import { displayFactBox, FactBoxOptions } from "./facts.js"
import ProcessManager from "./process-manager.js"
import type { Client } from "pg"
import PackageManager from "./package-manager.js"
const ADMIN_EMAIL = "admin@medusa-test.com"
let STORE_CORS = "http://localhost:8000"
@@ -24,6 +25,7 @@ type PreparePluginOptions = {
processManager: ProcessManager
abortController?: AbortController
verbose?: boolean
packageManager: PackageManager
}
type PrepareProjectOptions = {
@@ -42,6 +44,7 @@ type PrepareProjectOptions = {
nextjsDirectory?: string
client: Client | null
verbose?: boolean
packageManager: PackageManager
}
type PrepareOptions = PreparePluginOptions | PrepareProjectOptions
@@ -66,6 +69,7 @@ async function preparePlugin({
processManager,
abortController,
verbose = false,
packageManager,
}: PreparePluginOptions) {
// initialize execution options
const execOptions = {
@@ -98,20 +102,7 @@ async function preparePlugin({
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,
})
await packageManager.installDependencies(execOptions)
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
@@ -136,6 +127,7 @@ async function prepareProject({
nextjsDirectory = "",
client,
verbose = false,
packageManager,
}: PrepareProjectOptions) {
// initialize execution options
const execOptions = {
@@ -196,20 +188,7 @@ async function prepareProject({
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,
})
await packageManager.installDependencies(execOptions)
factBoxOptions.interval = displayFactBox({
...factBoxOptions,
@@ -285,18 +264,7 @@ async function prepareProject({
title: "Seeding database...",
})
await processManager.runProcess({
process: async () => {
try {
await execute([`yarn seed`, execOptions], { verbose })
} catch (e) {
// yarn isn't available
// use npm
await execute([`npm run seed`, execOptions], { verbose })
}
},
ignoreERESOLVE: true,
})
await packageManager.runCommand("seed", execOptions)
displayFactBox({
...factBoxOptions,

View File

@@ -3,6 +3,7 @@ import path from "path"
import createAbortController from "../create-abort-controller.js"
import { FactBoxOptions } from "../facts.js"
import ProcessManager from "../process-manager.js"
import PackageManager from "../package-manager.js"
export interface ProjectOptions {
repoUrl?: string
@@ -25,6 +26,7 @@ export interface ProjectCreator {
export abstract class BaseProjectCreator {
protected spinner: Ora
protected processManager: ProcessManager
protected packageManager: PackageManager
protected abortController: AbortController
protected factBoxOptions: FactBoxOptions
protected projectName: string
@@ -39,6 +41,7 @@ export abstract class BaseProjectCreator {
) {
this.spinner = ora()
this.processManager = new ProcessManager()
this.packageManager = new PackageManager(this.processManager)
this.abortController = createAbortController(this.processManager)
this.projectName = projectName
const basePath =

View File

@@ -72,6 +72,7 @@ export class PluginProjectCreator
processManager: this.processManager,
abortController: this.abortController,
verbose: this.options.verbose,
packageManager: this.packageManager,
})
}

View File

@@ -153,6 +153,7 @@ export class MedusaProjectCreator
nextjsDirectory: this.nextjsDirectory,
client: this.client,
verbose: this.options.verbose,
packageManager: this.packageManager,
})
} finally {
await this.client?.end()
@@ -214,18 +215,19 @@ export class MedusaProjectCreator
}
protected showSuccessMessage(): void {
const commandStr = this.packageManager.getCommandStr(`dev`)
logMessage({
message: boxen(
chalk.green(
`Change to the \`${
this.projectName
}\` directory to explore your Medusa project.${EOL}${EOL}Start your Medusa application again with the following command:${EOL}${EOL}yarn dev${EOL}${EOL}${
}\` directory to explore your Medusa project.${EOL}${EOL}Start your Medusa application again with the following command:${EOL}${EOL}${commandStr}${EOL}${EOL}${
this.inviteToken
? `After you start the Medusa application, you can create an admin user with the URL http://localhost:9000/app/invite?token=${this.inviteToken}&first_run=true${EOL}${EOL}`
: ""
}${
this.nextjsDirectory?.length
? `The Next.js Starter Storefront was installed in the \`${this.nextjsDirectory}\` directory. Change to that directory and start it with the following command:${EOL}${EOL}npm run dev${EOL}${EOL}`
? `The Next.js Starter Storefront was installed in the \`${this.nextjsDirectory}\` directory. Change to that directory and start it with the following command:${EOL}${EOL}${commandStr}${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`
),