Skip to content

Commit d303f2a

Browse files
authored
Extract install dependency helpers (#770)
This PR refactors install dependency helpers so they can be reused in other parts of the CLI package (spoiler alert).
1 parent 4039219 commit d303f2a

File tree

6 files changed

+102
-58
lines changed

6 files changed

+102
-58
lines changed

packages/cli/src/commands/init.ts

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,14 @@ import prompts, { PromptType } from 'prompts';
55
import { Config, ScriptConfig, ScriptName } from '../config';
66
import {
77
canRead,
8-
formatChildCommand,
9-
getPackageJsonDependencies,
10-
getPackageManagerInstallCommand,
8+
installMissingDependencies,
119
logBanner,
12-
logInfo,
1310
logSuccess,
14-
logWarning,
11+
PROMPT_OPTIONS,
1512
resolveRelativePath,
16-
spawnChildCommand,
1713
writeFile,
1814
} from '../utils';
1915

20-
const PROMPT_OPTIONS: prompts.Options = {
21-
onCancel: () => {
22-
throw new Error('Operation cancelled.');
23-
},
24-
};
25-
2616
export function setInitCommand(program: Command): void {
2717
program
2818
.command('init')
@@ -130,35 +120,12 @@ async function getPromptResult(
130120
PROMPT_OPTIONS,
131121
);
132122

133-
await installMissingDependencies(result);
134-
return result;
135-
}
136-
137-
async function installMissingDependencies(result: PromptResult): Promise<void> {
138-
const requiredDependencies = [
123+
await installMissingDependencies(`Your configuration requires additional dependencies.`, [
139124
...(result.scripts.includes('js') ? ['@codama/renderers-js'] : []),
140125
...(result.scripts.includes('rust') ? ['@codama/renderers-rust'] : []),
141-
];
142-
if (requiredDependencies.length === 0) return;
143-
144-
const installedDependencies = await getPackageJsonDependencies({ includeDev: true });
145-
const missingDependencies = requiredDependencies.filter(dep => !installedDependencies.includes(dep));
146-
if (missingDependencies.length === 0) return;
126+
]);
147127

148-
const installCommand = await getPackageManagerInstallCommand(missingDependencies);
149-
150-
logWarning(`Some dependencies are missing for the selected scripts.`);
151-
logWarning(`Install command: ${pico.yellow(formatChildCommand(installCommand))}`);
152-
153-
const dependencyResult: { installDependencies: boolean } = await prompts(
154-
{ initial: true, message: 'Install missing dependencies?', name: 'installDependencies', type: 'confirm' },
155-
PROMPT_OPTIONS,
156-
);
157-
if (!dependencyResult.installDependencies) return;
158-
159-
logInfo(`Installing: ${missingDependencies.join(', ')}`);
160-
await spawnChildCommand(installCommand, { quiet: true });
161-
logSuccess(`Dependencies installed successfully.`);
128+
return result;
162129
}
163130

164131
function getDefaultPromptResult(): PromptResult {

packages/cli/src/utils/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ export * from './fs';
33
export * from './import';
44
export * from './logs';
55
export * from './nodes';
6+
export * from './packageInstall';
67
export * from './packageJson';
78
export * from './packageManager';
89
export * from './promises';
10+
export * from './prompts';
911
export * from './visitors';

packages/cli/src/utils/logs.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,46 @@
11
import pico from 'picocolors';
22

3-
export function logSuccess(...args: unknown[]): void {
4-
console.log(pico.green('✔'), ...args);
3+
export function logSuccess(message: string, items?: string[]): void {
4+
console.log(pico.green('✔'), message);
5+
if (items) {
6+
logItems(items, pico.green);
7+
}
58
}
69

7-
export function logInfo(...args: unknown[]): void {
8-
console.log(pico.blueBright('→'), ...args);
10+
export function logInfo(message: string, items?: string[]): void {
11+
console.log(pico.blueBright('→'), message);
12+
if (items) {
13+
logItems(items, pico.blueBright);
14+
}
915
}
1016

11-
export function logWarning(...args: unknown[]): void {
12-
console.log(pico.yellow('▲'), ...args);
17+
export function logWarning(message: string, items?: string[]): void {
18+
console.log(pico.yellow('▲'), message);
19+
if (items) {
20+
logItems(items, pico.yellow);
21+
}
1322
}
1423

15-
export function logError(...args: unknown[]): void {
16-
console.log(pico.red('✖'), ...args);
24+
export function logError(message: string, items?: string[]): void {
25+
console.log(pico.red('✖'), message);
26+
if (items) {
27+
logItems(items, pico.red);
28+
}
1729
}
1830

19-
export function logDebug(...args: unknown[]): void {
20-
console.log(pico.magenta('✱'), ...args);
31+
export function logDebug(message: string, items?: string[]): void {
32+
console.log(pico.magenta('✱'), message);
33+
if (items) {
34+
logItems(items, pico.magenta);
35+
}
36+
}
37+
38+
export function logItems(items: string[], color?: (text: string) => string): void {
39+
const colorFn = color ?? (text => text);
40+
items.forEach((item, index) => {
41+
const prefix = index === items.length - 1 ? '└─' : '├─';
42+
console.log(' ' + colorFn(prefix), item);
43+
});
2144
}
2245

2346
export function logBanner(): void {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import pico from 'picocolors';
2+
import prompts from 'prompts';
3+
4+
import { ChildCommand, createChildCommand, formatChildCommand, spawnChildCommand } from './childCommands';
5+
import { logError, logInfo, logSuccess, logWarning } from './logs';
6+
import { getPackageJsonDependencies } from './packageJson';
7+
import { getPackageManager } from './packageManager';
8+
import { PROMPT_OPTIONS } from './prompts';
9+
10+
export async function getPackageManagerInstallCommand(
11+
packages: string[],
12+
options: string[] = [],
13+
): Promise<ChildCommand> {
14+
const packageManager = await getPackageManager();
15+
const args = [packageManager === 'yarn' ? 'add' : 'install', ...packages, ...options];
16+
return createChildCommand(packageManager, args);
17+
}
18+
19+
export async function installMissingDependencies(message: string, requiredDependencies: string[]): Promise<void> {
20+
if (requiredDependencies.length === 0) return;
21+
22+
const installedDependencies = await getPackageJsonDependencies({ includeDev: true });
23+
const missingDependencies = requiredDependencies.filter(dep => !installedDependencies.includes(dep));
24+
if (missingDependencies.length === 0) return;
25+
26+
await installDependencies(message, missingDependencies);
27+
}
28+
29+
export async function installDependencies(message: string, dependencies: string[]): Promise<void> {
30+
if (dependencies.length === 0) return;
31+
const installCommand = await getPackageManagerInstallCommand(dependencies);
32+
33+
logWarning(message);
34+
logWarning(`Install command: ${pico.yellow(formatChildCommand(installCommand))}`);
35+
36+
const dependencyResult: { installDependencies: boolean } = await prompts(
37+
{ initial: true, message: 'Install dependencies?', name: 'installDependencies', type: 'confirm' },
38+
PROMPT_OPTIONS,
39+
);
40+
if (!dependencyResult.installDependencies) {
41+
logWarning('Skipping installation. You can install manually later with:');
42+
logWarning(pico.yellow(formatChildCommand(installCommand)));
43+
return;
44+
}
45+
46+
try {
47+
logInfo(`Installing`, dependencies);
48+
await spawnChildCommand(installCommand, { quiet: true });
49+
logSuccess(`Dependencies installed successfully.`);
50+
} catch {
51+
logError(`Failed to install dependencies. Please try manually:`);
52+
logError(pico.yellow(formatChildCommand(installCommand)));
53+
}
54+
}

packages/cli/src/utils/packageManager.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChildCommand, createChildCommand, spawnChildCommand } from './childCommands';
1+
import { createChildCommand, spawnChildCommand } from './childCommands';
22
import { canRead, resolveRelativePath } from './fs';
33
import { getPackageJson } from './packageJson';
44

@@ -14,15 +14,6 @@ export async function getPackageManager(): Promise<PackageManager> {
1414
return packageManager;
1515
}
1616

17-
export async function getPackageManagerInstallCommand(
18-
packages: string[],
19-
options: string[] = [],
20-
): Promise<ChildCommand> {
21-
const packageManager = await getPackageManager();
22-
const args = [packageManager === 'yarn' ? 'add' : 'install', ...packages, ...options];
23-
return createChildCommand(packageManager, args);
24-
}
25-
2617
async function detectPackageManager(): Promise<PackageManager> {
2718
const fromPackageJson = await detectPackageManagerFromPackageJson();
2819
if (fromPackageJson) return fromPackageJson;

packages/cli/src/utils/prompts.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import prompts from 'prompts';
2+
3+
export const PROMPT_OPTIONS: prompts.Options = {
4+
onCancel: () => {
5+
throw new Error('Operation cancelled.');
6+
},
7+
};

0 commit comments

Comments
 (0)