summaryrefslogtreecommitdiff
path: root/packages/astro/src/cli/install-package.ts
blob: 8793d9985396ec17135b276d671284ced33aeedb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import boxen from 'boxen';
import { execa } from 'execa';
import { bold, cyan, dim, magenta } from 'kleur/colors';
import { createRequire } from 'node:module';
import ora from 'ora';
import prompts from 'prompts';
import whichPm from 'which-pm';
import { debug, info, type LogOptions } from '../core/logger/core.js';

type GetPackageOptions = {
	skipAsk?: boolean;
	cwd?: string;
};

export async function getPackage<T>(
	packageName: string,
	logging: LogOptions,
	options: GetPackageOptions,
	otherDeps: string[] = []
): Promise<T | undefined> {
	const require = createRequire(options.cwd ?? process.cwd());

	let packageImport;
	try {
		require.resolve(packageName);

		// The `require.resolve` is required as to avoid Node caching the failed `import`
		packageImport = await import(packageName);
	} catch (e) {
		info(
			logging,
			'',
			`To continue, Astro requires the following dependency to be installed: ${bold(packageName)}.`
		);
		const result = await installPackage([packageName, ...otherDeps], options, logging);

		if (result) {
			packageImport = await import(packageName);
		} else {
			return undefined;
		}
	}

	return packageImport as T;
}

function getInstallCommand(packages: string[], packageManager: string) {
	switch (packageManager) {
		case 'npm':
			return { pm: 'npm', command: 'install', flags: [], dependencies: packages };
		case 'yarn':
			return { pm: 'yarn', command: 'add', flags: [], dependencies: packages };
		case 'pnpm':
			return { pm: 'pnpm', command: 'add', flags: [], dependencies: packages };
		default:
			return null;
	}
}

async function installPackage(
	packageNames: string[],
	options: GetPackageOptions,
	logging: LogOptions
): Promise<boolean> {
	const cwd = options.cwd ?? process.cwd();
	const packageManager = (await whichPm(cwd)).name ?? 'npm';
	const installCommand = getInstallCommand(packageNames, packageManager);

	if (!installCommand) {
		return false;
	}

	const coloredOutput = `${bold(installCommand.pm)} ${installCommand.command}${[
		'',
		...installCommand.flags,
	].join(' ')} ${cyan(installCommand.dependencies.join(' '))}`;
	const message = `\n${boxen(coloredOutput, {
		margin: 0.5,
		padding: 0.5,
		borderStyle: 'round',
	})}\n`;
	info(
		logging,
		null,
		`\n  ${magenta('Astro will run the following command:')}\n  ${dim(
			'If you skip this step, you can always run it yourself later'
		)}\n${message}`
	);

	let response;
	if (options.skipAsk) {
		response = true;
	} else {
		response = (
			await prompts({
				type: 'confirm',
				name: 'askToContinue',
				message: 'Continue?',
				initial: true,
			})
		).askToContinue;
	}

	if (Boolean(response)) {
		const spinner = ora('Installing dependencies...').start();
		try {
			await execa(
				installCommand.pm,
				[installCommand.command, ...installCommand.flags, ...installCommand.dependencies],
				{ cwd: cwd }
			);
			spinner.succeed();

			return true;
		} catch (err) {
			debug('add', 'Error installing dependencies', err);
			spinner.fail();

			return false;
		}
	} else {
		return false;
	}
}