发布于

Node.js工具开发:构建高效的CLI工具和开发工具链

作者

Node.js工具开发:构建高效的CLI工具和开发工具链

Node.js不仅适合构建Web应用,也是开发各种工具的理想平台。本文将详细介绍如何使用Node.js构建CLI工具、构建系统和开发工具链。

CLI工具开发基础

项目结构和依赖配置

{
  "name": "nodejs-dev-tools",
  "version": "1.0.0",
  "description": "Node.js Development Tools Collection",
  "main": "src/index.js",
  "bin": {
    "devtools": "./bin/devtools.js",
    "file-organizer": "./bin/file-organizer.js",
    "project-generator": "./bin/project-generator.js",
    "build-optimizer": "./bin/build-optimizer.js"
  },
  "scripts": {
    "start": "node src/index.js",
    "dev": "nodemon src/index.js",
    "build": "webpack --mode production",
    "test": "jest",
    "lint": "eslint src/",
    "publish": "npm publish"
  },
  "dependencies": {
    "commander": "^11.0.0",
    "inquirer": "^9.2.10",
    "chalk": "^5.3.0",
    "ora": "^7.0.1",
    "figlet": "^1.6.0",
    "fs-extra": "^11.1.1",
    "glob": "^10.3.3",
    "chokidar": "^3.5.3",
    "yargs": "^17.7.2",
    "boxen": "^7.1.1",
    "progress": "^2.0.3",
    "semver": "^7.5.4",
    "axios": "^1.5.0",
    "cheerio": "^1.0.0-rc.12",
    "sharp": "^0.32.5",
    "archiver": "^6.0.1",
    "node-cron": "^3.0.2"
  },
  "devDependencies": {
    "jest": "^29.6.2",
    "nodemon": "^3.0.1",
    "eslint": "^8.47.0",
    "webpack": "^5.88.2",
    "webpack-cli": "^5.1.4"
  },
  "preferGlobal": true,
  "keywords": ["cli", "tools", "development", "nodejs"],
  "author": "Your Name",
  "license": "MIT"
}

核心CLI框架

// bin/devtools.js - 主CLI入口
#!/usr/bin/env node

const { Command } = require('commander');
const chalk = require('chalk');
const figlet = require('figlet');
const boxen = require('boxen');
const packageJson = require('../package.json');

// 导入子命令
const fileOrganizer = require('../src/commands/fileOrganizer');
const projectGenerator = require('../src/commands/projectGenerator');
const buildOptimizer = require('../src/commands/buildOptimizer');
const codeAnalyzer = require('../src/commands/codeAnalyzer');

const program = new Command();

// 显示欢迎信息
function showWelcome() {
  console.log(
    chalk.cyan(
      figlet.textSync('DevTools', {
        font: 'Standard',
        horizontalLayout: 'default',
        verticalLayout: 'default'
      })
    )
  );
  
  console.log(
    boxen(
      chalk.white(`🚀 Node.js Development Tools v${packageJson.version}\n`) +
      chalk.gray('A collection of powerful development utilities'),
      {
        padding: 1,
        margin: 1,
        borderStyle: 'round',
        borderColor: 'cyan'
      }
    )
  );
}

// 配置主程序
program
  .name('devtools')
  .description('Node.js Development Tools Collection')
  .version(packageJson.version)
  .option('-v, --verbose', 'enable verbose output')
  .option('--no-color', 'disable colored output')
  .hook('preAction', (thisCommand) => {
    if (thisCommand.opts().verbose) {
      process.env.VERBOSE = 'true';
    }
    if (thisCommand.opts().noColor) {
      process.env.NO_COLOR = 'true';
    }
  });

// 文件整理工具
program
  .command('organize')
  .alias('org')
  .description('organize files by type, date, or custom rules')
  .argument('<directory>', 'directory to organize')
  .option('-t, --type <type>', 'organization type (type|date|size)', 'type')
  .option('-d, --dry-run', 'preview changes without executing')
  .option('-r, --recursive', 'process subdirectories recursively')
  .action(fileOrganizer.organize);

// 项目生成器
program
  .command('generate')
  .alias('gen')
  .description('generate new project from templates')
  .argument('[template]', 'template name (react|vue|express|cli)')
  .option('-n, --name <name>', 'project name')
  .option('-d, --directory <dir>', 'target directory')
  .option('--git', 'initialize git repository')
  .option('--install', 'install dependencies automatically')
  .action(projectGenerator.generate);

// 构建优化器
program
  .command('optimize')
  .alias('opt')
  .description('optimize build output and assets')
  .argument('[directory]', 'build directory to optimize', './dist')
  .option('-i, --images', 'optimize images')
  .option('-j, --javascript', 'minify JavaScript files')
  .option('-c, --css', 'minify CSS files')
  .option('-g, --gzip', 'create gzip compressed files')
  .action(buildOptimizer.optimize);

// 代码分析器
program
  .command('analyze')
  .description('analyze code quality and metrics')
  .argument('[directory]', 'directory to analyze', './src')
  .option('-f, --format <format>', 'output format (json|html|console)', 'console')
  .option('-o, --output <file>', 'output file path')
  .option('--complexity', 'analyze code complexity')
  .option('--dependencies', 'analyze dependencies')
  .action(codeAnalyzer.analyze);

// 监听模式
program
  .command('watch')
  .description('watch files and execute commands on changes')
  .argument('<pattern>', 'file pattern to watch')
  .argument('<command>', 'command to execute on changes')
  .option('-i, --ignore <patterns...>', 'patterns to ignore')
  .option('-d, --delay <ms>', 'delay before executing command', '500')
  .action(require('../src/commands/watcher').watch);

// 帮助命令
program
  .command('help')
  .description('show detailed help information')
  .action(() => {
    showWelcome();
    program.help();
  });

// 错误处理
program.exitOverride();

try {
  // 如果没有参数,显示欢迎信息和帮助
  if (process.argv.length <= 2) {
    showWelcome();
    program.help();
  } else {
    program.parse();
  }
} catch (err) {
  if (err.code === 'commander.help') {
    process.exit(0);
  } else if (err.code === 'commander.version') {
    process.exit(0);
  } else {
    console.error(chalk.red('Error:'), err.message);
    process.exit(1);
  }
}

文件整理工具

// src/commands/fileOrganizer.js - 文件整理工具
const fs = require('fs-extra');
const path = require('path');
const chalk = require('chalk');
const ora = require('ora');
const inquirer = require('inquirer');
const glob = require('glob');

class FileOrganizer {
  constructor() {
    this.fileTypes = {
      images: ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg', '.webp', '.ico'],
      documents: ['.pdf', '.doc', '.docx', '.txt', '.rtf', '.odt', '.pages'],
      spreadsheets: ['.xls', '.xlsx', '.csv', '.ods', '.numbers'],
      presentations: ['.ppt', '.pptx', '.odp', '.key'],
      videos: ['.mp4', '.avi', '.mkv', '.mov', '.wmv', '.flv', '.webm'],
      audio: ['.mp3', '.wav', '.flac', '.aac', '.ogg', '.m4a'],
      archives: ['.zip', '.rar', '.7z', '.tar', '.gz', '.bz2'],
      code: ['.js', '.ts', '.py', '.java', '.cpp', '.c', '.html', '.css', '.php'],
      fonts: ['.ttf', '.otf', '.woff', '.woff2', '.eot'],
      executables: ['.exe', '.msi', '.dmg', '.pkg', '.deb', '.rpm', '.app']
    };
  }

  async organize(directory, options) {
    try {
      // 验证目录
      if (!await fs.pathExists(directory)) {
        console.error(chalk.red(`Directory does not exist: ${directory}`));
        return;
      }

      const spinner = ora('Scanning directory...').start();
      
      // 获取文件列表
      const pattern = options.recursive ? '**/*' : '*';
      const files = glob.sync(path.join(directory, pattern), {
        nodir: true,
        dot: false
      });

      spinner.succeed(`Found ${files.length} files`);

      if (files.length === 0) {
        console.log(chalk.yellow('No files found to organize'));
        return;
      }

      // 根据组织类型处理文件
      let organizationPlan;
      
      switch (options.type) {
        case 'type':
          organizationPlan = this.organizeByType(files, directory);
          break;
        case 'date':
          organizationPlan = await this.organizeByDate(files, directory);
          break;
        case 'size':
          organizationPlan = await this.organizeBySize(files, directory);
          break;
        default:
          console.error(chalk.red(`Unknown organization type: ${options.type}`));
          return;
      }

      // 显示组织计划
      this.displayPlan(organizationPlan);

      // 确认执行
      if (options.dryRun) {
        console.log(chalk.yellow('\nDry run mode - no files were moved'));
        return;
      }

      const { confirm } = await inquirer.prompt([
        {
          type: 'confirm',
          name: 'confirm',
          message: 'Do you want to proceed with the organization?',
          default: false
        }
      ]);

      if (!confirm) {
        console.log(chalk.yellow('Organization cancelled'));
        return;
      }

      // 执行文件移动
      await this.executePlan(organizationPlan);
      
      console.log(chalk.green('\n✅ File organization completed successfully!'));

    } catch (error) {
      console.error(chalk.red('Error organizing files:'), error.message);
    }
  }

  organizeByType(files, baseDir) {
    const plan = new Map();

    files.forEach(filePath => {
      const ext = path.extname(filePath).toLowerCase();
      const fileName = path.basename(filePath);
      
      let category = 'others';
      
      // 查找文件类型分类
      for (const [type, extensions] of Object.entries(this.fileTypes)) {
        if (extensions.includes(ext)) {
          category = type;
          break;
        }
      }

      const targetDir = path.join(baseDir, category);
      const targetPath = path.join(targetDir, fileName);

      if (!plan.has(category)) {
        plan.set(category, []);
      }

      plan.get(category).push({
        source: filePath,
        target: targetPath,
        targetDir: targetDir
      });
    });

    return plan;
  }

  async organizeByDate(files, baseDir) {
    const plan = new Map();

    for (const filePath of files) {
      try {
        const stats = await fs.stat(filePath);
        const date = stats.mtime;
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, '0');
        
        const category = `${year}/${year}-${month}`;
        const fileName = path.basename(filePath);
        const targetDir = path.join(baseDir, category);
        const targetPath = path.join(targetDir, fileName);

        if (!plan.has(category)) {
          plan.set(category, []);
        }

        plan.get(category).push({
          source: filePath,
          target: targetPath,
          targetDir: targetDir
        });
      } catch (error) {
        console.warn(chalk.yellow(`Warning: Could not read stats for ${filePath}`));
      }
    }

    return plan;
  }

  async organizeBySize(files, baseDir) {
    const plan = new Map();
    const sizeCategories = {
      'small': { min: 0, max: 1024 * 1024 }, // < 1MB
      'medium': { min: 1024 * 1024, max: 10 * 1024 * 1024 }, // 1MB - 10MB
      'large': { min: 10 * 1024 * 1024, max: 100 * 1024 * 1024 }, // 10MB - 100MB
      'huge': { min: 100 * 1024 * 1024, max: Infinity } // > 100MB
    };

    for (const filePath of files) {
      try {
        const stats = await fs.stat(filePath);
        const size = stats.size;
        
        let category = 'unknown';
        
        for (const [catName, range] of Object.entries(sizeCategories)) {
          if (size >= range.min && size < range.max) {
            category = catName;
            break;
          }
        }

        const fileName = path.basename(filePath);
        const targetDir = path.join(baseDir, category);
        const targetPath = path.join(targetDir, fileName);

        if (!plan.has(category)) {
          plan.set(category, []);
        }

        plan.get(category).push({
          source: filePath,
          target: targetPath,
          targetDir: targetDir
        });
      } catch (error) {
        console.warn(chalk.yellow(`Warning: Could not read stats for ${filePath}`));
      }
    }

    return plan;
  }

  displayPlan(plan) {
    console.log(chalk.cyan('\n📋 Organization Plan:'));
    console.log(chalk.gray('─'.repeat(50)));

    for (const [category, files] of plan) {
      console.log(chalk.blue(`\n📁 ${category} (${files.length} files)`));
      
      // 显示前5个文件作为示例
      const displayFiles = files.slice(0, 5);
      displayFiles.forEach(file => {
        const relativePath = path.relative(process.cwd(), file.source);
        console.log(chalk.gray(`${relativePath}`));
      });
      
      if (files.length > 5) {
        console.log(chalk.gray(`  ... and ${files.length - 5} more files`));
      }
    }
  }

  async executePlan(plan) {
    const spinner = ora('Moving files...').start();
    let movedCount = 0;
    let totalFiles = 0;

    // 计算总文件数
    for (const files of plan.values()) {
      totalFiles += files.length;
    }

    try {
      for (const [category, files] of plan) {
        // 创建目标目录
        const targetDir = files[0].targetDir;
        await fs.ensureDir(targetDir);

        // 移动文件
        for (const file of files) {
          try {
            // 检查目标文件是否已存在
            let targetPath = file.target;
            let counter = 1;
            
            while (await fs.pathExists(targetPath)) {
              const ext = path.extname(file.target);
              const name = path.basename(file.target, ext);
              const dir = path.dirname(file.target);
              targetPath = path.join(dir, `${name}_${counter}${ext}`);
              counter++;
            }

            await fs.move(file.source, targetPath);
            movedCount++;
            
            spinner.text = `Moving files... (${movedCount}/${totalFiles})`;
          } catch (error) {
            console.warn(chalk.yellow(`Warning: Could not move ${file.source}: ${error.message}`));
          }
        }
      }

      spinner.succeed(`Successfully moved ${movedCount} files`);
    } catch (error) {
      spinner.fail('Error during file organization');
      throw error;
    }
  }
}

module.exports = {
  organize: (directory, options) => {
    const organizer = new FileOrganizer();
    return organizer.organize(directory, options);
  }
};

项目生成器

// src/commands/projectGenerator.js - 项目生成器
const fs = require('fs-extra');
const path = require('path');
const chalk = require('chalk');
const ora = require('ora');
const inquirer = require('inquirer');
const { execSync } = require('child_process');

class ProjectGenerator {
  constructor() {
    this.templates = {
      react: {
        name: 'React Application',
        description: 'Modern React app with TypeScript and Vite',
        dependencies: ['react', 'react-dom', 'typescript', 'vite', '@vitejs/plugin-react'],
        devDependencies: ['@types/react', '@types/react-dom', 'eslint', 'prettier']
      },
      vue: {
        name: 'Vue.js Application',
        description: 'Vue 3 app with TypeScript and Vite',
        dependencies: ['vue', 'vue-router', 'pinia', 'typescript'],
        devDependencies: ['@vitejs/plugin-vue', 'vite', 'eslint', 'prettier']
      },
      express: {
        name: 'Express.js API',
        description: 'RESTful API with Express.js and TypeScript',
        dependencies: ['express', 'cors', 'helmet', 'morgan', 'dotenv'],
        devDependencies: ['@types/express', '@types/cors', 'typescript', 'nodemon', 'ts-node']
      },
      cli: {
        name: 'CLI Tool',
        description: 'Command-line tool with Commander.js',
        dependencies: ['commander', 'chalk', 'inquirer', 'ora'],
        devDependencies: ['typescript', '@types/node', 'jest', '@types/jest']
      }
    };
  }

  async generate(template, options) {
    try {
      // 如果没有指定模板,显示选择菜单
      if (!template) {
        const { selectedTemplate } = await inquirer.prompt([
          {
            type: 'list',
            name: 'selectedTemplate',
            message: 'Select a project template:',
            choices: Object.entries(this.templates).map(([key, config]) => ({
              name: `${config.name} - ${config.description}`,
              value: key
            }))
          }
        ]);
        template = selectedTemplate;
      }

      // 验证模板
      if (!this.templates[template]) {
        console.error(chalk.red(`Unknown template: ${template}`));
        console.log(chalk.yellow('Available templates:'), Object.keys(this.templates).join(', '));
        return;
      }

      // 获取项目信息
      const projectInfo = await this.getProjectInfo(options);
      
      // 创建项目
      await this.createProject(template, projectInfo);
      
      console.log(chalk.green('\n🎉 Project created successfully!'));
      console.log(chalk.cyan('\nNext steps:'));
      console.log(chalk.gray(`  cd ${projectInfo.name}`));
      
      if (!projectInfo.install) {
        console.log(chalk.gray('  npm install'));
      }
      
      console.log(chalk.gray('  npm run dev'));

    } catch (error) {
      console.error(chalk.red('Error generating project:'), error.message);
    }
  }

  async getProjectInfo(options) {
    const questions = [];

    if (!options.name) {
      questions.push({
        type: 'input',
        name: 'name',
        message: 'Project name:',
        validate: (input) => {
          if (!input.trim()) return 'Project name is required';
          if (!/^[a-z0-9-_]+$/i.test(input)) return 'Invalid project name';
          return true;
        }
      });
    }

    if (!options.directory) {
      questions.push({
        type: 'input',
        name: 'directory',
        message: 'Target directory:',
        default: (answers) => answers.name || options.name || '.'
      });
    }

    if (options.git === undefined) {
      questions.push({
        type: 'confirm',
        name: 'git',
        message: 'Initialize Git repository?',
        default: true
      });
    }

    if (options.install === undefined) {
      questions.push({
        type: 'confirm',
        name: 'install',
        message: 'Install dependencies automatically?',
        default: true
      });
    }

    const answers = await inquirer.prompt(questions);

    return {
      name: options.name || answers.name,
      directory: options.directory || answers.directory,
      git: options.git !== undefined ? options.git : answers.git,
      install: options.install !== undefined ? options.install : answers.install
    };
  }

  async createProject(template, projectInfo) {
    const templateConfig = this.templates[template];
    const projectPath = path.resolve(projectInfo.directory);

    // 检查目录是否已存在
    if (await fs.pathExists(projectPath)) {
      const { overwrite } = await inquirer.prompt([
        {
          type: 'confirm',
          name: 'overwrite',
          message: `Directory ${projectPath} already exists. Overwrite?`,
          default: false
        }
      ]);

      if (!overwrite) {
        console.log(chalk.yellow('Project creation cancelled'));
        return;
      }

      await fs.remove(projectPath);
    }

    const spinner = ora('Creating project structure...').start();

    try {
      // 创建项目目录
      await fs.ensureDir(projectPath);

      // 生成package.json
      await this.generatePackageJson(projectPath, projectInfo, templateConfig);

      // 生成项目文件
      await this.generateProjectFiles(projectPath, template, projectInfo);

      // 生成配置文件
      await this.generateConfigFiles(projectPath, template);

      spinner.succeed('Project structure created');

      // 初始化Git仓库
      if (projectInfo.git) {
        const gitSpinner = ora('Initializing Git repository...').start();
        try {
          execSync('git init', { cwd: projectPath, stdio: 'ignore' });
          execSync('git add .', { cwd: projectPath, stdio: 'ignore' });
          execSync('git commit -m "Initial commit"', { cwd: projectPath, stdio: 'ignore' });
          gitSpinner.succeed('Git repository initialized');
        } catch (error) {
          gitSpinner.warn('Git initialization failed');
        }
      }

      // 安装依赖
      if (projectInfo.install) {
        const installSpinner = ora('Installing dependencies...').start();
        try {
          execSync('npm install', { cwd: projectPath, stdio: 'ignore' });
          installSpinner.succeed('Dependencies installed');
        } catch (error) {
          installSpinner.fail('Failed to install dependencies');
          console.log(chalk.yellow('You can install them manually with: npm install'));
        }
      }

    } catch (error) {
      spinner.fail('Failed to create project');
      throw error;
    }
  }

  async generatePackageJson(projectPath, projectInfo, templateConfig) {
    const packageJson = {
      name: projectInfo.name,
      version: '1.0.0',
      description: '',
      main: 'index.js',
      scripts: this.getScripts(templateConfig),
      keywords: [],
      author: '',
      license: 'MIT',
      dependencies: {},
      devDependencies: {}
    };

    // 添加依赖
    templateConfig.dependencies.forEach(dep => {
      packageJson.dependencies[dep] = 'latest';
    });

    templateConfig.devDependencies.forEach(dep => {
      packageJson.devDependencies[dep] = 'latest';
    });

    await fs.writeJson(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 });
  }

  getScripts(templateConfig) {
    const scripts = {
      react: {
        dev: 'vite',
        build: 'vite build',
        preview: 'vite preview',
        lint: 'eslint src --ext .ts,.tsx',
        'lint:fix': 'eslint src --ext .ts,.tsx --fix'
      },
      vue: {
        dev: 'vite',
        build: 'vite build',
        preview: 'vite preview',
        lint: 'eslint src --ext .ts,.vue',
        'lint:fix': 'eslint src --ext .ts,.vue --fix'
      },
      express: {
        start: 'node dist/index.js',
        dev: 'nodemon src/index.ts',
        build: 'tsc',
        test: 'jest',
        lint: 'eslint src --ext .ts',
        'lint:fix': 'eslint src --ext .ts --fix'
      },
      cli: {
        start: 'node dist/index.js',
        dev: 'ts-node src/index.ts',
        build: 'tsc',
        test: 'jest',
        lint: 'eslint src --ext .ts',
        'lint:fix': 'eslint src --ext .ts --fix'
      }
    };

    return scripts[templateConfig.name.toLowerCase().split(' ')[0]] || {};
  }

  async generateProjectFiles(projectPath, template, projectInfo) {
    const templatesDir = path.join(__dirname, '../../templates', template);
    
    // 如果模板目录存在,复制文件
    if (await fs.pathExists(templatesDir)) {
      await fs.copy(templatesDir, projectPath);
    } else {
      // 生成基本文件结构
      await this.generateBasicStructure(projectPath, template, projectInfo);
    }
  }

  async generateBasicStructure(projectPath, template, projectInfo) {
    const structures = {
      react: {
        'src/App.tsx': this.getReactAppContent(),
        'src/main.tsx': this.getReactMainContent(),
        'index.html': this.getReactIndexContent(projectInfo.name),
        'vite.config.ts': this.getViteConfigContent()
      },
      vue: {
        'src/App.vue': this.getVueAppContent(),
        'src/main.ts': this.getVueMainContent(),
        'index.html': this.getVueIndexContent(projectInfo.name),
        'vite.config.ts': this.getVueViteConfigContent()
      },
      express: {
        'src/index.ts': this.getExpressIndexContent(),
        'src/routes/index.ts': this.getExpressRoutesContent(),
        'tsconfig.json': this.getTsConfigContent()
      },
      cli: {
        'src/index.ts': this.getCliIndexContent(),
        'bin/cli.js': this.getCliBinContent(),
        'tsconfig.json': this.getTsConfigContent()
      }
    };

    const files = structures[template] || {};

    for (const [filePath, content] of Object.entries(files)) {
      const fullPath = path.join(projectPath, filePath);
      await fs.ensureDir(path.dirname(fullPath));
      await fs.writeFile(fullPath, content);
    }
  }

  async generateConfigFiles(projectPath, template) {
    // 生成通用配置文件
    const configs = {
      '.gitignore': this.getGitignoreContent(),
      'README.md': this.getReadmeContent(template),
      '.eslintrc.js': this.getEslintConfigContent(),
      '.prettierrc': this.getPrettierConfigContent()
    };

    for (const [fileName, content] of Object.entries(configs)) {
      await fs.writeFile(path.join(projectPath, fileName), content);
    }
  }

  // 内容生成方法
  getReactAppContent() {
    return `import React from 'react';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>Welcome to React!</h1>
        <p>Edit src/App.tsx and save to reload.</p>
      </header>
    </div>
  );
}

export default App;
`;
  }

  getReactMainContent() {
    return `import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
`;
  }

  getReactIndexContent(projectName) {
    return `<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>${projectName}</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>
`;
  }

  getViteConfigContent() {
    return `import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
});
`;
  }

  getGitignoreContent() {
    return `# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Production builds
dist/
build/

# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Coverage directory used by tools like istanbul
coverage/
*.lcov

# nyc test coverage
.nyc_output

# Dependency directories
jspm_packages/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity
`;
  }

  getReadmeContent(template) {
    return `# ${template.charAt(0).toUpperCase() + template.slice(1)} Project

This project was generated using the DevTools CLI.

## Getting Started

\`\`\`bash
npm install
npm run dev
\`\`\`

## Available Scripts

- \`npm run dev\` - Start development server
- \`npm run build\` - Build for production
- \`npm run lint\` - Run ESLint
- \`npm run test\` - Run tests

## Project Structure

\`\`\`
src/
├── components/
├── utils/
└── index.ts
\`\`\`

## Contributing

1. Fork the repository
2. Create your feature branch
3. Commit your changes
4. Push to the branch
5. Create a Pull Request
`;
  }

  getEslintConfigContent() {
    return `module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module',
  },
  plugins: [
    '@typescript-eslint',
  ],
  rules: {
    // Add your custom rules here
  },
};
`;
  }

  getPrettierConfigContent() {
    return `{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2
}
`;
  }

  getTsConfigContent() {
    return `{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
`;
  }

  getExpressIndexContent() {
    return `import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import dotenv from 'dotenv';
import routes from './routes';

dotenv.config();

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Routes
app.use('/api', routes);

// Health check
app.get('/health', (req, res) => {
  res.json({ status: 'OK', timestamp: new Date().toISOString() });
});

app.listen(PORT, () => {
  console.log(\`Server running on port \${PORT}\`);
});
`;
  }

  getExpressRoutesContent() {
    return `import { Router } from 'express';

const router = Router();

router.get('/', (req, res) => {
  res.json({ message: 'API is working!' });
});

export default router;
`;
  }

  getCliIndexContent() {
    return `#!/usr/bin/env node

import { Command } from 'commander';
import chalk from 'chalk';

const program = new Command();

program
  .name('my-cli')
  .description('CLI tool description')
  .version('1.0.0');

program
  .command('hello')
  .description('Say hello')
  .argument('[name]', 'name to greet', 'World')
  .action((name) => {
    console.log(chalk.green(\`Hello, \${name}!\`));
  });

program.parse();
`;
  }

  getCliBinContent() {
    return `#!/usr/bin/env node
require('../dist/index.js');
`;
  }
}

module.exports = {
  generate: (template, options) => {
    const generator = new ProjectGenerator();
    return generator.generate(template, options);
  }
};

总结

Node.js工具开发的核心要点:

🎯 工具类型

  1. CLI工具:命令行界面和交互
  2. 构建工具:自动化构建和优化
  3. 开发工具:代码生成和分析
  4. 系统工具:文件处理和监控

✅ 核心技术

  • Commander.js命令行框架
  • Inquirer.js交互式提示
  • Chalk颜色输出和美化
  • 文件系统操作和处理

🚀 高级功能

  • 项目模板生成
  • 代码分析和优化
  • 文件监听和自动化
  • 插件系统和扩展

💡 最佳实践

  • 用户体验设计
  • 错误处理和验证
  • 配置管理和扩展性
  • 测试和文档完善

掌握Node.js工具开发,提升开发效率!


Node.js的丰富生态系统和强大的文件处理能力使其成为开发各种工具的理想平台,通过合理的设计可以构建出高效、易用的开发工具链。