codes-command-tool
v1.0.1
Published
A powerful Node.js command execution tool that supports executing commands in specified directories
Maintainers
Readme
Codes Command Tool
一个强大的 Node.js 工具库,提供命令执行和文件操作的完整解决方案。支持在指定目录执行命令、文件系统操作、补丁应用等功能,提供完整的 TypeScript 类型定义。
✨ 主要特性
🚀 命令执行工具
- 指定目录执行: 在任意目录执行命令,无需切换工作目录
- 多种执行模式: 基础模式、简化模式和批量执行模式
- 完善的错误处理: 超时机制、退出代码检查、详细错误信息
- 丰富的配置选项: 超时、环境变量、进程选项等
📁 文件系统工具
- 目录结构读取: 递归读取目录,支持过滤、排序、深度控制
- 文件内容操作: 智能读取文件内容,支持编码检测和类型判断
- 安全文件创建: 防路径穿越,支持目录自动创建和覆盖控制
- 文件删除管理: 安全删除文件和目录,支持递归删除
- 补丁应用系统: 基于 diff 的补丁应用,支持多文件补丁
🛡️ 安全与可靠性
- 完整类型支持: 使用 TypeScript 编写,提供完整的类型定义
- 路径安全: 防止路径穿越攻击,安全的文件操作
- 错误处理: 完善的错误处理和异常捕获机制
- 异步优先: 基于 Promise 的 API,支持 async/await
🚀 快速开始
安装
npm install codes-command-tool
# 或
pnpm add codes-command-tool
# 或
yarn add codes-command-tool基础用法
命令执行
import { executeCommand, executeCommandSimple } from 'codes-command-tool';
// 基础用法:在指定目录执行命令
const result = await executeCommand('/path/to/directory', 'ls', ['-la']);
console.log('退出代码:', result.code);
console.log('输出:', result.stdout);
console.log('执行时间:', result.duration, 'ms');
// 简化用法:直接获取输出
const output = await executeCommandSimple('/path/to/directory', 'pwd');
console.log('当前目录:', output);文件操作
import {
readFolder,
readFileContent,
createFile,
deleteFile,
formatFileTree
} from 'codes-command-tool';
// 读取目录结构
const fileTree = await readFolder('/path/to/directory');
console.log(formatFileTree(fileTree, true)); // 显示文件大小
// 输出示例:
// 📁 my-project/
// ├── 总文件数: 15
// ├── 总目录数: 4
// └── 总大小: 2.34 MB
//
// ├── 📁 src
// │ ├── 📄 index.ts (1.2 KB)
// │ ├── 📄 utils.ts (856 B)
// │ └── 📁 components
// │ ├── 📄 Button.tsx (2.1 KB)
// │ └── 📄 Modal.tsx (3.4 KB)
// ├── 📄 package.json (1.8 KB)
// ├── 📄 README.md (4.2 KB)
// └── 📄 tsconfig.json (512 B)
// 读取文件内容
const fileInfo = await readFileContent('/path/to/file.txt');
console.log('文件内容:', fileInfo.content);
console.log('是否为文本文件:', fileInfo.isText);
// 创建文件
const newFile = await createFile('/path/to/directory', 'new-file.txt', 'Hello World!');
console.log('文件已创建:', newFile.path);
// 删除文件
await deleteFile('/path/to/directory', 'old-file.txt');高级用法
命令执行高级功能
import { executeCommand, executeMultipleCommands } from 'codes-command-tool';
// 带选项的执行
const result = await executeCommand(
'./my-project',
'npm',
['install'],
{
timeout: 60000, // 60秒超时
env: { NODE_ENV: 'production' }, // 自定义环境变量
inheritEnv: true // 继承当前环境变量
}
);
// 批量执行命令
const results = await executeMultipleCommands('/path/to/project', [
{ command: 'npm', args: ['install'] },
{ command: 'npm', args: ['run', 'build'] },
{ command: 'npm', args: ['test'] }
]);文件系统高级功能
import {
readFolder,
readFileContent,
createFile,
deleteFolder,
PatchApplier,
FileType
} from 'codes-command-tool';
// 高级目录读取:过滤、排序和忽略目录
const fileTree = await readFolder('/path/to/project', {
recursive: true,
maxDepth: 3,
includeHidden: false,
ignoreFolders: ['node_modules', '.git', 'dist', 'coverage'], // 忽略常见的构建目录
filter: (file) => {
// 只包含 .ts 和 .js 文件,以及目录
return file.type === FileType.DIRECTORY ||
file.name.endsWith('.ts') ||
file.name.endsWith('.js');
},
sort: (a, b) => a.name.localeCompare(b.name)
});
// 格式化并显示文件树
console.log('项目结构:');
console.log(formatFileTree(fileTree, true)); // 显示文件大小
// 智能文件内容读取
const fileInfo = await readFileContent('/path/to/file.txt', {
encoding: 'utf8',
detectType: true,
maxSize: 1024 * 1024 // 1MB 限制
});
// 安全文件创建(支持嵌套路径)
const newFile = await createFile(
'/base/directory',
'nested/path/file.txt',
'Content',
{
allowNestedPath: true,
ensureDir: true,
overwrite: false
}
);
// 应用补丁
const patchApplier = new PatchApplier({
workdir: '/path/to/project',
patchContent: patchString,
dryRun: false
});
const results = await patchApplier.applyAll();
results.forEach(result => {
console.log(`${result.path}: ${result.success ? '成功' : '失败'}`);
if (!result.success) {
console.error(result.error);
}
});📚 API 文档
命令执行工具
executeCommand
在指定目录执行命令的主要函数。
function executeCommand(
directory: string,
command: string,
args?: string[],
options?: ExecuteOptions
): Promise<CommandResult>参数:
directory- 执行命令的目录路径(相对或绝对路径)command- 要执行的命令args- 命令参数数组(可选)options- 执行选项(可选)
返回:
interface CommandResult {
code: number; // 退出代码
stdout: string; // 标准输出
stderr: string; // 标准错误输出
duration: number; // 执行时间(毫秒)
}executeCommandSimple
简化版本,仅返回标准输出,失败时抛出异常。
function executeCommandSimple(
directory: string,
command: string,
args?: string[],
options?: ExecuteOptions
): Promise<string>executeMultipleCommands
在指定目录执行多个命令。
function executeMultipleCommands(
directory: string,
commands: CommandConfig[],
options?: ExecuteOptions
): Promise<CommandResult[]>ExecuteOptions
interface ExecuteOptions {
timeout?: number; // 超时时间(毫秒),默认 30000
inheritEnv?: boolean; // 是否继承当前环境变量,默认 true
env?: NodeJS.ProcessEnv; // 自定义环境变量
// ... 其他 Node.js spawn 选项
}文件系统工具
readFolder
读取目录结构,支持递归、过滤、排序等功能。
function readFolder(
dirPath: string,
options?: ReadFolderOptions
): Promise<FileTree>参数:
dirPath- 目录路径options- 读取选项(可选)
返回:
interface FileTree {
root: FileInfo; // 根目录信息
totalFiles: number; // 文件总数
totalDirectories: number; // 目录总数
totalSize: number; // 总大小(字节)
}
interface ReadFolderOptions {
recursive?: boolean; // 是否递归读取,默认 true
maxDepth?: number; // 最大递归深度,默认 无限制
includeHidden?: boolean; // 是否包含隐藏文件,默认 true
ignoreFolders?: string[]; // 忽略的目录名称列表,默认 []
filter?: (file: FileInfo) => boolean; // 文件过滤器
sort?: (a: FileInfo, b: FileInfo) => number; // 排序函数
}formatFileTree
将文件树转换为可视化的文本格式显示。
function formatFileTree(
fileTree: FileTree,
showSize?: boolean
): string参数:
fileTree- 通过readFolder获取的文件树showSize- 是否显示文件大小,默认 false
返回:
格式化的文本字符串,包含:
- 树形结构显示(使用
├──和└──符号) - 文件和目录图标(📁 目录,📄 文件)
- 统计信息(总文件数、总目录数、总大小)
- 可选的文件大小显示(当
showSize为 true 时)
readFileContent
智能读取文件内容,支持编码检测和类型判断。
function readFileContent(
filePath: string,
options?: ReadFileContentOptions
): Promise<FileContentInfo>返回:
interface FileContentInfo {
path: string; // 文件路径
name: string; // 文件名
extension: string; // 文件扩展名
size: number; // 文件大小(字节)
lastModified: Date; // 最后修改时间
content: string | Buffer; // 文件内容
encoding: BufferEncoding | 'binary'; // 文件编码
isText: boolean; // 是否为文本文件
lineCount?: number; // 行数(仅文本文件)
}createFile
在指定目录创建文件,支持安全路径检查和目录自动创建。
function createFile(
directoryPath: string,
fileName: string,
content?: string | Buffer,
options?: CreateFileOptions
): Promise<FileInfo>deleteFile / deleteFolder
安全删除文件或目录。
function deleteFile(
directoryPath: string,
fileName: string,
options?: DeleteFileOptions
): Promise<void>
function deleteFolder(
directoryPath: string,
folderName: string,
options?: DeleteFolderOptions
): Promise<void>PatchApplier
基于 diff 的补丁应用系统。
class PatchApplier {
constructor(options: PatchApplierOptions)
async applyAll(): Promise<PatchResult[]>
getParsedPatches(): StructuredPatch[]
getFileCount(): number
}
interface PatchApplierOptions {
workdir: string; // 工作目录
patchContent: string; // 补丁文件内容
encoding?: BufferEncoding; // 文件编码,默认 'utf8'
dryRun?: boolean; // 仅试运行,不写入磁盘
}💡 使用示例
项目文件分析
import { readFolder, readFileContent, formatFileTree, FileType } from 'codes-command-tool';
// 分析项目结构
const projectTree = await readFolder('./my-project', {
recursive: true,
maxDepth: 3,
ignoreFolders: ['node_modules', '.git', 'dist', 'coverage', '.next', 'build'], // 使用 ignoreFolders 更简洁
filter: (file) => {
// 进一步过滤,只关注源代码文件
if (file.type === FileType.DIRECTORY) return true;
return file.name.match(/\.(ts|js|tsx|jsx|vue|py|java|go|rs)$/);
}
});
console.log('项目结构:');
console.log(formatFileTree(projectTree, true)); // 显示文件大小
console.log(`\n📊 项目统计:`);
console.log(`- 总文件数: ${projectTree.totalFiles}`);
console.log(`- 总目录数: ${projectTree.totalDirectories}`);
console.log(`- 总大小: ${(projectTree.totalSize / 1024 / 1024).toFixed(2)} MB`);
// 读取所有 TypeScript 文件
const tsFiles = [];
function collectTsFiles(files) {
for (const file of files) {
if (file.type === FileType.FILE && file.name.endsWith('.ts')) {
tsFiles.push(file.path);
}
if (file.children) {
collectTsFiles(file.children);
}
}
}
collectTsFiles(projectTree.root.children || []);
// 分析每个 TypeScript 文件
for (const filePath of tsFiles) {
const fileInfo = await readFileContent(filePath);
console.log(`${fileInfo.name}: ${fileInfo.lineCount} 行, ${fileInfo.size} 字节`);
}自动化构建工具
import {
executeCommand,
executeMultipleCommands,
createFile,
readFileContent
} from 'codes-command-tool';
// 创建构建脚本
async function buildProject(projectPath: string) {
console.log('开始构建项目...');
// 检查 package.json 是否存在
try {
const packageJson = await readFileContent(`${projectPath}/package.json`);
console.log('找到 package.json');
} catch {
console.error('未找到 package.json,创建默认配置...');
await createFile(projectPath, 'package.json', JSON.stringify({
name: 'my-project',
version: '1.0.0',
scripts: {
build: 'tsc',
test: 'jest'
}
}, null, 2));
}
// 执行构建步骤
const buildSteps = [
{ command: 'npm', args: ['install'] },
{ command: 'npm', args: ['run', 'lint'] },
{ command: 'npm', args: ['run', 'test'] },
{ command: 'npm', args: ['run', 'build'] }
];
const results = await executeMultipleCommands(projectPath, buildSteps, {
timeout: 300000 // 5分钟超时
});
// 检查构建结果
const success = results.every(result => result.code === 0);
if (success) {
console.log('✅ 构建成功!');
// 创建构建报告
const report = results.map((result, index) =>
`步骤 ${index + 1}: ${buildSteps[index].command} ${buildSteps[index].args?.join(' ')} - ${result.duration}ms`
).join('\n');
await createFile(projectPath, 'build-report.txt', report);
} else {
console.log('❌ 构建失败');
results.forEach((result, index) => {
if (result.code !== 0) {
console.error(`步骤 ${index + 1} 失败:`, result.stderr);
}
});
}
}
// 使用示例
await buildProject('./my-project');代码补丁管理
import { PatchApplier, readFileContent, createFile } from 'codes-command-tool';
// 应用代码补丁
async function applyCodePatch(projectPath: string, patchFilePath: string) {
// 读取补丁文件
const patchFile = await readFileContent(patchFilePath);
const patchContent = patchFile.content as string;
// 创建补丁应用器
const patchApplier = new PatchApplier({
workdir: projectPath,
patchContent,
dryRun: false // 设为 true 可以预览而不实际应用
});
console.log(`准备应用补丁到 ${patchApplier.getFileCount()} 个文件...`);
// 应用补丁
const results = await patchApplier.applyAll();
// 生成应用报告
const successCount = results.filter(r => r.success).length;
const failCount = results.length - successCount;
console.log(`补丁应用完成: ${successCount} 成功, ${failCount} 失败`);
// 保存详细报告
const report = results.map(result =>
`${result.path}: ${result.success ? '✅ 成功' : '❌ 失败'}${
result.error ? ` - ${result.error}` : ''
}`
).join('\n');
await createFile(projectPath, 'patch-report.txt', report);
return { successCount, failCount, results };
}
// 使用示例
const patchResult = await applyCodePatch('./my-project', './fixes.patch');Git 操作与版本管理
import { executeCommand, executeCommandSimple, createFile } from 'codes-command-tool';
// Git 仓库状态检查
async function checkGitStatus(repoPath: string) {
try {
// 检查是否是 Git 仓库
await executeCommand(repoPath, 'git', ['rev-parse', '--git-dir']);
// 获取当前分支
const branch = await executeCommandSimple(repoPath, 'git', [
'branch', '--show-current'
]);
// 检查工作区状态
const status = await executeCommand(repoPath, 'git', ['status', '--porcelain']);
const hasChanges = status.stdout.trim().length > 0;
// 获取最近提交信息
const lastCommit = await executeCommandSimple(repoPath, 'git', [
'log', '-1', '--pretty=format:%h %s'
]);
return {
branch: branch.trim(),
hasChanges,
lastCommit: lastCommit.trim(),
changes: status.stdout.trim().split('\n').filter(line => line.trim())
};
} catch (error) {
throw new Error('不是有效的 Git 仓库或 Git 不可用');
}
}
// 自动提交工具
async function autoCommit(repoPath: string, message: string) {
const gitStatus = await checkGitStatus(repoPath);
if (!gitStatus.hasChanges) {
console.log('没有需要提交的更改');
return;
}
console.log(`在分支 ${gitStatus.branch} 上发现 ${gitStatus.changes.length} 个更改`);
// 添加所有更改
await executeCommand(repoPath, 'git', ['add', '.']);
// 提交更改
const commitResult = await executeCommand(repoPath, 'git', ['commit', '-m', message]);
if (commitResult.code === 0) {
console.log('✅ 提交成功');
// 创建提交报告
const report = `提交时间: ${new Date().toISOString()}
分支: ${gitStatus.branch}
提交信息: ${message}
更改文件:
${gitStatus.changes.join('\n')}`;
await createFile(repoPath, '.git-commit-log.txt', report, { overwrite: true });
} else {
console.error('❌ 提交失败:', commitResult.stderr);
}
}
// 使用示例
const gitInfo = await checkGitStatus('./my-project');
console.log(`当前分支: ${gitInfo.branch}`);
console.log(`最近提交: ${gitInfo.lastCommit}`);
if (gitInfo.hasChanges) {
await autoCommit('./my-project', 'feat: 自动提交工具更新');
}错误处理
try {
const result = await executeCommand('/nonexistent', 'ls');
} catch (error) {
console.error('命令执行失败:', error.message);
}
// 或者检查退出代码
const result = await executeCommand('.', 'ls', ['/nonexistent']);
if (result.code !== 0) {
console.error('命令失败:', result.stderr);
}⚠️ 注意事项
安全考虑
- 谨慎处理用户输入,避免命令注入攻击
- 不要执行不受信任的命令
- 使用参数数组而不是字符串拼接来避免 shell 注入
// ✅ 安全:使用参数数组
await executeCommand('/path', 'ls', ['-la', userInput]);
// ❌ 不安全:避免字符串拼接
// await executeCommand('/path', `ls -la ${userInput}`);平台兼容性
- 该库在 Windows、macOS 和 Linux 上都可正常工作
- 命令需要在目标系统上可用
- Windows 上建议使用 PowerShell 或 Git Bash
性能考虑
- 大量并发执行可能消耗系统资源
- 合理设置超时时间
- 考虑使用
executeMultipleCommands串行执行相关命令
🎯 最佳实践
错误处理
// 推荐:明确处理错误情况
try {
const result = await executeCommand('./project', 'npm', ['test']);
if (result.code !== 0) {
console.error('测试失败:', result.stderr);
process.exit(1);
}
} catch (error) {
console.error('执行错误:', error.message);
process.exit(1);
}路径处理
import { resolve } from 'path';
// 推荐:使用绝对路径
const projectPath = resolve('./my-project');
await executeCommand(projectPath, 'npm', ['install']);
// 或使用相对路径(相对于当前工作目录)
await executeCommand('./my-project', 'npm', ['install']);环境变量管理
// 推荐:明确设置所需的环境变量
await executeCommand('./project', 'npm', ['run', 'build'], {
env: {
NODE_ENV: 'production',
CI: 'true'
},
inheritEnv: true // 继承其他环境变量
});🔧 开发和构建
项目要求
- Node.js: >= 20.0.0
- 包管理器: 推荐使用 pnpm
- TypeScript: 5.8+
主要依赖
- diff: 用于补丁解析和应用
- 开发依赖: TypeScript、Jest、Rollup、TypeDoc 等
开发流程
如果您想要参与开发或自定义构建:
# 克隆项目
git clone <repository-url>
cd codes-command-tool
# 安装依赖
pnpm install
# 开发模式(监听文件变化)
pnpm run dev
# 构建项目
pnpm run build
# 运行测试
pnpm run test
# 测试覆盖率
pnpm run test:coverage
# 代码检查和格式化
pnpm run lint
pnpm run format
# 生成文档
pnpm run docs
# 清理构建文件
pnpm run clean构建输出
项目支持多种模块格式:
- ESM:
dist/index.js(推荐) - CommonJS:
dist/index.cjs - TypeScript 声明:
dist/index.d.ts - 文档:
docs/目录
🐛 常见问题
命令执行相关
Q: 如何处理长时间运行的命令?
A: 使用 timeout 选项设置合适的超时时间,或设置为 0 禁用超时:
await executeCommand('./project', 'npm', ['install'], {
timeout: 0 // 禁用超时
});Q: 如何在 Windows 上使用?
A: 确保使用正确的命令名称。Windows 上某些命令可能需要 .exe 后缀:
// Windows 上可能需要
await executeCommand('.', 'npm.exe', ['--version']);
// 或使用 PowerShell
await executeCommand('.', 'powershell', ['-Command', 'Get-Location']);Q: 如何处理需要交互输入的命令?
A: 这个库不支持交互式命令。请使用非交互式标志或预先配置所需的输入:
// 使用非交互式标志
await executeCommand('.', 'npm', ['install', '--yes']);Q: 命令执行失败但没有抛出错误?
A: executeCommand 不会因为非零退出代码而抛出错误。请检查 result.code:
const result = await executeCommand('.', 'false'); // 总是返回退出代码 1
if (result.code !== 0) {
console.error('命令失败');
}
// 或使用 executeCommandSimple,它会在失败时抛出错误
try {
await executeCommandSimple('.', 'false');
} catch (error) {
console.error('命令失败:', error.message);
}文件操作相关
Q: 如何安全地处理用户输入的文件路径?
A: 默认情况下,文件操作函数会阻止路径穿越攻击。如果需要支持嵌套路径,请明确设置:
// 安全:默认阻止路径穿越
await createFile('/base/dir', 'file.txt', 'content');
// 如果需要嵌套路径,明确允许
await createFile('/base/dir', 'nested/path/file.txt', 'content', {
allowNestedPath: true
});Q: 如何处理大文件读取?
A: 使用 maxSize 选项限制文件大小,避免内存溢出:
try {
const fileInfo = await readFileContent('/path/to/large-file.txt', {
maxSize: 10 * 1024 * 1024 // 限制 10MB
});
} catch (error) {
console.error('文件过大或读取失败:', error.message);
}Q: 补丁应用失败怎么办?
A: 使用 dryRun 模式预览补丁效果,检查补丁格式和目标文件:
// 先预览补丁效果
const patchApplier = new PatchApplier({
workdir: './project',
patchContent: patchString,
dryRun: true // 仅预览,不实际修改
});
const results = await patchApplier.applyAll();
results.forEach(result => {
if (!result.success) {
console.error(`${result.path}: ${result.error}`);
}
});Q: 如何处理文件编码问题?
A: 库会自动检测文件编码,也可以手动指定:
// 自动检测编码
const fileInfo = await readFileContent('/path/to/file.txt', {
detectType: true
});
// 手动指定编码
const fileInfo = await readFileContent('/path/to/file.txt', {
encoding: 'gbk' // 指定编码
});
console.log('检测到的编码:', fileInfo.encoding);
console.log('是否为文本文件:', fileInfo.isText);📄 许可证
本项目采用 MIT 许可证。
🤝 贡献
欢迎提交 Issue 和 Pull Request 来改进这个工具库!
贡献指南
- Fork 项目
- 创建特性分支 (
git checkout -b feature/amazing-feature) - 提交更改 (
git commit -m 'Add some amazing feature') - 推送到分支 (
git push origin feature/amazing-feature) - 打开一个 Pull Request
提示: 这个库为您提供了安全、可靠的命令执行能力,让您专注于业务逻辑而不用担心底层实现!
