1import * as PackageManager from '@expo/package-manager';
2import { execSync } from 'child_process';
3
4import { CLI_NAME } from './cmd';
5
6export type PackageManagerName = 'npm' | 'pnpm' | 'yarn' | 'bun';
7
8const debug = require('debug')('expo:init:resolvePackageManager') as typeof console.log;
9
10/** Determine which package manager to use for installing dependencies based on how the process was started. */
11export function resolvePackageManager(): PackageManagerName {
12  // Attempt to detect if the user started the command using `yarn` or `pnpm` or `bun`
13  const userAgent = process.env.npm_config_user_agent;
14  debug('npm_config_user_agent:', userAgent);
15  if (userAgent?.startsWith('yarn')) {
16    return 'yarn';
17  } else if (userAgent?.startsWith('pnpm')) {
18    return 'pnpm';
19  } else if (userAgent?.startsWith('bun')) {
20    return 'bun';
21  } else if (userAgent?.startsWith('npm')) {
22    return 'npm';
23  }
24
25  // Try availability
26  if (isPackageManagerAvailable('yarn')) {
27    return 'yarn';
28  } else if (isPackageManagerAvailable('pnpm')) {
29    return 'pnpm';
30  } else if (isPackageManagerAvailable('bun')) {
31    return 'bun';
32  }
33
34  return 'npm';
35}
36
37export function isPackageManagerAvailable(manager: PackageManagerName): boolean {
38  try {
39    execSync(`${manager} --version`, { stdio: 'ignore' });
40    return true;
41  } catch {}
42  return false;
43}
44
45export function formatRunCommand(packageManager: PackageManagerName, cmd: string) {
46  switch (packageManager) {
47    case 'pnpm':
48      return `pnpm run ${cmd}`;
49    case 'yarn':
50      return `yarn ${cmd}`;
51    case 'bun':
52      return `bun run ${cmd}`;
53    case 'npm':
54    default:
55      return `npm run ${cmd}`;
56  }
57}
58
59export function formatSelfCommand() {
60  const packageManager = resolvePackageManager();
61  switch (packageManager) {
62    case 'pnpm':
63      return `pnpx ${CLI_NAME}`;
64    case 'bun':
65      return `bunx ${CLI_NAME}`;
66    case 'yarn':
67    case 'npm':
68    default:
69      return `npx ${CLI_NAME}`;
70  }
71}
72
73export async function installDependenciesAsync(
74  projectRoot: string,
75  packageManager: PackageManagerName,
76  flags: { silent: boolean } = { silent: false }
77) {
78  const options = { cwd: projectRoot, silent: flags.silent };
79  if (packageManager === 'yarn') {
80    await new PackageManager.YarnPackageManager(options).installAsync();
81  } else if (packageManager === 'pnpm') {
82    await new PackageManager.PnpmPackageManager(options).installAsync();
83  } else if (packageManager === 'bun') {
84    await new PackageManager.BunPackageManager(options).installAsync();
85  } else {
86    await new PackageManager.NpmPackageManager(options).installAsync();
87  }
88}
89