浏览代码

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).
Loris Leiva 3 月之前
父节点
当前提交
d303f2a809

+ 5 - 38
packages/cli/src/commands/init.ts

@@ -5,24 +5,14 @@ import prompts, { PromptType } from 'prompts';
 import { Config, ScriptConfig, ScriptName } from '../config';
 import {
     canRead,
-    formatChildCommand,
-    getPackageJsonDependencies,
-    getPackageManagerInstallCommand,
+    installMissingDependencies,
     logBanner,
-    logInfo,
     logSuccess,
-    logWarning,
+    PROMPT_OPTIONS,
     resolveRelativePath,
-    spawnChildCommand,
     writeFile,
 } from '../utils';
 
-const PROMPT_OPTIONS: prompts.Options = {
-    onCancel: () => {
-        throw new Error('Operation cancelled.');
-    },
-};
-
 export function setInitCommand(program: Command): void {
     program
         .command('init')
@@ -130,35 +120,12 @@ async function getPromptResult(
         PROMPT_OPTIONS,
     );
 
-    await installMissingDependencies(result);
-    return result;
-}
-
-async function installMissingDependencies(result: PromptResult): Promise<void> {
-    const requiredDependencies = [
+    await installMissingDependencies(`Your configuration requires additional dependencies.`, [
         ...(result.scripts.includes('js') ? ['@codama/renderers-js'] : []),
         ...(result.scripts.includes('rust') ? ['@codama/renderers-rust'] : []),
-    ];
-    if (requiredDependencies.length === 0) return;
-
-    const installedDependencies = await getPackageJsonDependencies({ includeDev: true });
-    const missingDependencies = requiredDependencies.filter(dep => !installedDependencies.includes(dep));
-    if (missingDependencies.length === 0) return;
+    ]);
 
-    const installCommand = await getPackageManagerInstallCommand(missingDependencies);
-
-    logWarning(`Some dependencies are missing for the selected scripts.`);
-    logWarning(`Install command: ${pico.yellow(formatChildCommand(installCommand))}`);
-
-    const dependencyResult: { installDependencies: boolean } = await prompts(
-        { initial: true, message: 'Install missing dependencies?', name: 'installDependencies', type: 'confirm' },
-        PROMPT_OPTIONS,
-    );
-    if (!dependencyResult.installDependencies) return;
-
-    logInfo(`Installing: ${missingDependencies.join(', ')}`);
-    await spawnChildCommand(installCommand, { quiet: true });
-    logSuccess(`Dependencies installed successfully.`);
+    return result;
 }
 
 function getDefaultPromptResult(): PromptResult {

+ 2 - 0
packages/cli/src/utils/index.ts

@@ -3,7 +3,9 @@ export * from './fs';
 export * from './import';
 export * from './logs';
 export * from './nodes';
+export * from './packageInstall';
 export * from './packageJson';
 export * from './packageManager';
 export * from './promises';
+export * from './prompts';
 export * from './visitors';

+ 33 - 10
packages/cli/src/utils/logs.ts

@@ -1,23 +1,46 @@
 import pico from 'picocolors';
 
-export function logSuccess(...args: unknown[]): void {
-    console.log(pico.green('✔'), ...args);
+export function logSuccess(message: string, items?: string[]): void {
+    console.log(pico.green('✔'), message);
+    if (items) {
+        logItems(items, pico.green);
+    }
 }
 
-export function logInfo(...args: unknown[]): void {
-    console.log(pico.blueBright('→'), ...args);
+export function logInfo(message: string, items?: string[]): void {
+    console.log(pico.blueBright('→'), message);
+    if (items) {
+        logItems(items, pico.blueBright);
+    }
 }
 
-export function logWarning(...args: unknown[]): void {
-    console.log(pico.yellow('▲'), ...args);
+export function logWarning(message: string, items?: string[]): void {
+    console.log(pico.yellow('▲'), message);
+    if (items) {
+        logItems(items, pico.yellow);
+    }
 }
 
-export function logError(...args: unknown[]): void {
-    console.log(pico.red('✖'), ...args);
+export function logError(message: string, items?: string[]): void {
+    console.log(pico.red('✖'), message);
+    if (items) {
+        logItems(items, pico.red);
+    }
 }
 
-export function logDebug(...args: unknown[]): void {
-    console.log(pico.magenta('✱'), ...args);
+export function logDebug(message: string, items?: string[]): void {
+    console.log(pico.magenta('✱'), message);
+    if (items) {
+        logItems(items, pico.magenta);
+    }
+}
+
+export function logItems(items: string[], color?: (text: string) => string): void {
+    const colorFn = color ?? (text => text);
+    items.forEach((item, index) => {
+        const prefix = index === items.length - 1 ? '└─' : '├─';
+        console.log('  ' + colorFn(prefix), item);
+    });
 }
 
 export function logBanner(): void {

+ 54 - 0
packages/cli/src/utils/packageInstall.ts

@@ -0,0 +1,54 @@
+import pico from 'picocolors';
+import prompts from 'prompts';
+
+import { ChildCommand, createChildCommand, formatChildCommand, spawnChildCommand } from './childCommands';
+import { logError, logInfo, logSuccess, logWarning } from './logs';
+import { getPackageJsonDependencies } from './packageJson';
+import { getPackageManager } from './packageManager';
+import { PROMPT_OPTIONS } from './prompts';
+
+export async function getPackageManagerInstallCommand(
+    packages: string[],
+    options: string[] = [],
+): Promise<ChildCommand> {
+    const packageManager = await getPackageManager();
+    const args = [packageManager === 'yarn' ? 'add' : 'install', ...packages, ...options];
+    return createChildCommand(packageManager, args);
+}
+
+export async function installMissingDependencies(message: string, requiredDependencies: string[]): Promise<void> {
+    if (requiredDependencies.length === 0) return;
+
+    const installedDependencies = await getPackageJsonDependencies({ includeDev: true });
+    const missingDependencies = requiredDependencies.filter(dep => !installedDependencies.includes(dep));
+    if (missingDependencies.length === 0) return;
+
+    await installDependencies(message, missingDependencies);
+}
+
+export async function installDependencies(message: string, dependencies: string[]): Promise<void> {
+    if (dependencies.length === 0) return;
+    const installCommand = await getPackageManagerInstallCommand(dependencies);
+
+    logWarning(message);
+    logWarning(`Install command: ${pico.yellow(formatChildCommand(installCommand))}`);
+
+    const dependencyResult: { installDependencies: boolean } = await prompts(
+        { initial: true, message: 'Install dependencies?', name: 'installDependencies', type: 'confirm' },
+        PROMPT_OPTIONS,
+    );
+    if (!dependencyResult.installDependencies) {
+        logWarning('Skipping installation. You can install manually later with:');
+        logWarning(pico.yellow(formatChildCommand(installCommand)));
+        return;
+    }
+
+    try {
+        logInfo(`Installing`, dependencies);
+        await spawnChildCommand(installCommand, { quiet: true });
+        logSuccess(`Dependencies installed successfully.`);
+    } catch {
+        logError(`Failed to install dependencies. Please try manually:`);
+        logError(pico.yellow(formatChildCommand(installCommand)));
+    }
+}

+ 1 - 10
packages/cli/src/utils/packageManager.ts

@@ -1,4 +1,4 @@
-import { ChildCommand, createChildCommand, spawnChildCommand } from './childCommands';
+import { createChildCommand, spawnChildCommand } from './childCommands';
 import { canRead, resolveRelativePath } from './fs';
 import { getPackageJson } from './packageJson';
 
@@ -14,15 +14,6 @@ export async function getPackageManager(): Promise<PackageManager> {
     return packageManager;
 }
 
-export async function getPackageManagerInstallCommand(
-    packages: string[],
-    options: string[] = [],
-): Promise<ChildCommand> {
-    const packageManager = await getPackageManager();
-    const args = [packageManager === 'yarn' ? 'add' : 'install', ...packages, ...options];
-    return createChildCommand(packageManager, args);
-}
-
 async function detectPackageManager(): Promise<PackageManager> {
     const fromPackageJson = await detectPackageManagerFromPackageJson();
     if (fromPackageJson) return fromPackageJson;

+ 7 - 0
packages/cli/src/utils/prompts.ts

@@ -0,0 +1,7 @@
+import prompts from 'prompts';
+
+export const PROMPT_OPTIONS: prompts.Options = {
+    onCancel: () => {
+        throw new Error('Operation cancelled.');
+    },
+};