From 90f1122839ae2ae7e901529bffa8700b5a36e9d7 Mon Sep 17 00:00:00 2001 From: Rob Di Marco Date: Tue, 9 Sep 2025 15:12:35 -0400 Subject: [PATCH] Add dependency checks with helpful error messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add dependency checking utility for git and npm - Check dependencies before running demo and create commands - Show friendly error messages with installation instructions - Add tests for dependency checking functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/atxp/src/check-dependencies.test.ts | 30 ++++++++ packages/atxp/src/check-dependencies.ts | 75 ++++++++++++++++++++ packages/atxp/src/index.ts | 50 ++++++++----- packages/atxp/src/run-demo.ts | 1 - 4 files changed, 139 insertions(+), 17 deletions(-) create mode 100644 packages/atxp/src/check-dependencies.test.ts create mode 100644 packages/atxp/src/check-dependencies.ts diff --git a/packages/atxp/src/check-dependencies.test.ts b/packages/atxp/src/check-dependencies.test.ts new file mode 100644 index 0000000..2f6ac4a --- /dev/null +++ b/packages/atxp/src/check-dependencies.test.ts @@ -0,0 +1,30 @@ +import { describe, it, expect } from 'vitest'; +import { checkDependency } from './check-dependencies.js'; + +describe('checkDependency', () => { + it('should return true for available commands', async () => { + const nodeDependency = { + name: 'Node.js', + command: 'node', + args: ['--version'], + requiredFor: ['demo'], + installInstructions: 'Please install Node.js' + }; + + const result = await checkDependency(nodeDependency); + expect(result).toBe(true); + }); + + it('should return false for unavailable commands', async () => { + const fakeDependency = { + name: 'Fake Command', + command: 'nonexistent-command-12345', + args: ['--version'], + requiredFor: ['demo'], + installInstructions: 'This command does not exist' + }; + + const result = await checkDependency(fakeDependency); + expect(result).toBe(false); + }); +}); \ No newline at end of file diff --git a/packages/atxp/src/check-dependencies.ts b/packages/atxp/src/check-dependencies.ts new file mode 100644 index 0000000..913bc95 --- /dev/null +++ b/packages/atxp/src/check-dependencies.ts @@ -0,0 +1,75 @@ +import { spawn } from 'child_process'; +import chalk from 'chalk'; + +interface DependencyCheck { + name: string; + command: string; + args: string[]; + requiredFor: string[]; + installInstructions: string; +} + +const DEPENDENCIES: DependencyCheck[] = [ + { + name: 'Git', + command: 'git', + args: ['--version'], + requiredFor: ['demo', 'create'], + installInstructions: 'Please install Git from https://git-scm.com/downloads' + }, + { + name: 'npm', + command: 'npm', + args: ['--version'], + requiredFor: ['demo'], + installInstructions: 'Please install Node.js (which includes npm) from https://nodejs.org/' + } +]; + +export async function checkDependency(dependency: DependencyCheck): Promise { + return new Promise((resolve) => { + const process = spawn(dependency.command, dependency.args, { + stdio: 'ignore' + }); + + process.on('close', (code) => { + resolve(code === 0); + }); + + process.on('error', () => { + resolve(false); + }); + }); +} + +export async function checkAllDependencies(command?: string): Promise { + let allDependenciesOk = true; + + for (const dependency of DEPENDENCIES) { + // If a specific command is provided, only check dependencies for that command + if (command && !dependency.requiredFor.includes(command)) { + continue; + } + + const isAvailable = await checkDependency(dependency); + + if (!isAvailable) { + console.error(chalk.red(`✗ ${dependency.name} is not installed or not available in PATH`)); + console.error(chalk.yellow(` Required for: ${dependency.requiredFor.join(', ')}`)); + console.error(chalk.white(` ${dependency.installInstructions}`)); + console.error(''); + allDependenciesOk = false; + } + } + + return allDependenciesOk; +} + +export function showDependencyError(command: string): void { + console.error(chalk.red(`Cannot run "${command}" command due to missing dependencies.`)); + console.error(chalk.yellow('Please install the required dependencies and try again.')); + console.error(''); + console.error(chalk.white('For help with installation, visit:')); + console.error(chalk.blue(' Git: https://git-scm.com/downloads')); + console.error(chalk.blue(' Node.js/npm: https://nodejs.org/')); +} \ No newline at end of file diff --git a/packages/atxp/src/index.ts b/packages/atxp/src/index.ts index 7be22ad..d3fff4f 100644 --- a/packages/atxp/src/index.ts +++ b/packages/atxp/src/index.ts @@ -5,6 +5,7 @@ import os from 'os'; import { createProject } from './create-project.js'; import { runDemo } from './run-demo.js'; import { showHelp } from './help.js'; +import { checkAllDependencies, showDependencyError } from './check-dependencies.js'; interface DemoOptions { port: number; @@ -54,20 +55,37 @@ const isCreateMode = process.env.npm_config_argv?.includes('create') || command === 'create'; // Handle different commands -if (isCreateMode) { - console.log('Creating new ATXP project...'); - createProject(); -} else if (command === 'demo') { - console.log('Starting ATXP demo...'); - runDemo(demoOptions); -} else if (command === 'help' || command === '--help' || command === '-h') { - showHelp(); -} else if (!command) { - // No command provided - show help instead of running demo - showHelp(); -} else { - // Unknown command - console.log(`Unknown command: ${command}`); - console.log('Run "npx atxp help" for usage information.'); +async function main() { + if (isCreateMode) { + console.log('Creating new ATXP project...'); + const dependenciesOk = await checkAllDependencies('create'); + if (!dependenciesOk) { + showDependencyError('create'); + process.exit(1); + } + createProject(); + } else if (command === 'demo') { + console.log('Starting ATXP demo...'); + const dependenciesOk = await checkAllDependencies('demo'); + if (!dependenciesOk) { + showDependencyError('demo'); + process.exit(1); + } + runDemo(demoOptions); + } else if (command === 'help' || command === '--help' || command === '-h') { + showHelp(); + } else if (!command) { + // No command provided - show help instead of running demo + showHelp(); + } else { + // Unknown command + console.log(`Unknown command: ${command}`); + console.log('Run "npx atxp help" for usage information.'); + process.exit(1); + } +} + +main().catch((error) => { + console.error('An unexpected error occurred:', error.message); process.exit(1); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/packages/atxp/src/run-demo.ts b/packages/atxp/src/run-demo.ts index 504334d..81ed154 100644 --- a/packages/atxp/src/run-demo.ts +++ b/packages/atxp/src/run-demo.ts @@ -1,7 +1,6 @@ import chalk from 'chalk'; import { spawn } from 'child_process'; import fs from 'fs-extra'; -import path from 'path'; import open from 'open'; interface DemoOptions {