batch-exec-cli 1.2.2 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present, chandq
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## 功能特性
6
6
 
7
- - 🚀 高效遍历目标目录的所有直接子目录
7
+ - 🚀 高效遍历目标目录的所有直接子目录, 默认并行执行
8
8
  - 📁 支持绝对路径和相对路径
9
9
  - 🚫 可配置忽略目录(支持 `.gitignore` 风格的模式匹配)
10
10
  - 📊 提供执行摘要和失败目录列表
@@ -64,6 +64,7 @@ batch-exec ./repos ls -la
64
64
  | `-s, --skip <文件>` | | 指定忽略文件路径(默认:`./.batchexecignore`) |
65
65
  | `-v, --verbose` | | 显示详细输出 |
66
66
  | `--no-progress` | | 禁用进度条显示 |
67
+ | `--no-parallel` | | 禁用并行执行, 按顺序执行 |
67
68
  | `-h, --help` | | 显示帮助信息 |
68
69
 
69
70
  ### 使用自定义忽略文件
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "batch-exec-cli",
3
- "version": "1.2.2",
4
- "description": "Efficiently iterate through directories and execute commands with progress display",
3
+ "version": "1.3.1",
4
+ "description": "Efficiently iterate through directories and execute commands with progress display and parallel execution",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "bin": {
package/src/cli.js CHANGED
@@ -10,7 +10,7 @@ $.verbose = false;
10
10
 
11
11
  async function main() {
12
12
  const argv = minimist(process.argv.slice(2), {
13
- boolean: ['v', 'verbose', 'h', 'help', 'no-progress'],
13
+ boolean: ['v', 'verbose', 'h', 'help', 'no-progress', 'no-parallel'],
14
14
  string: ['s', 'skip'],
15
15
  alias: {
16
16
  s: 'skip',
@@ -45,6 +45,7 @@ async function main() {
45
45
  console.log(bold('\n🚀 Batch Executor\n'));
46
46
  console.log(`Target directory: ${cyan(targetDir)}`);
47
47
  console.log(`Command: ${yellow(command)} ${args.join(' ')}`);
48
+ console.log(`Parallel mode: ${argv.parallel === false ? red('Disabled') : green('Enabled')}`);
48
49
  if (skipPaths.length > 0) {
49
50
  console.log(`Skipping directories: ${gray(skipPaths.join(', '))}`);
50
51
  }
@@ -55,7 +56,8 @@ async function main() {
55
56
  const results = await batchExecute(targetDir, command, args, {
56
57
  skipPaths,
57
58
  verbose: argv.verbose,
58
- showProgress: argv.progress !== false
59
+ showProgress: argv.progress !== false,
60
+ parallel: argv.parallel !== false
59
61
  });
60
62
 
61
63
  printSummary(results);
@@ -67,7 +69,7 @@ async function main() {
67
69
 
68
70
  function printHelp() {
69
71
  console.log(`
70
- ${bold('Batch Executor')} ${dim('v1.1.0')}
72
+ ${bold('Batch Executor')} ${dim('v1.3.0')}
71
73
 
72
74
  ${cyan('Usage:')} batch-exec [options] <directory> <command> [args...]
73
75
 
@@ -82,12 +84,14 @@ ${magenta('Options:')}
82
84
  -s, --skip <file> Ignore file path (default: ./.batchexecignore)
83
85
  -v, --verbose Show verbose output
84
86
  --no-progress Disable progress bar
87
+ --no-parallel Disable parallel execution (use sequential mode)
85
88
  -h, --help Show this help message
86
89
 
87
90
  ${green('Examples:')}
88
91
  ${green('batch-exec')} ./my-projects git pull
89
92
  ${green('batch-exec')} ./my-projects npm update lodash -S
90
93
  ${green('batch-exec')} --skip ./custom-ignore.txt ./repos ls -la
94
+ ${green('batch-exec')} --no-parallel ./my-projects npm install
91
95
  `);
92
96
  }
93
97
 
package/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import path from 'path';
2
- import { $, cd } from 'zx';
2
+ import { $, within, cd } from 'zx';
3
3
  import { parseIgnoreFile } from './ignoreParser.js';
4
4
  import { listDirectSubdirectories } from './directoryLister.js';
5
5
  import { cyan, red, ProgressBar, clearLine } from './utils/colors.js';
@@ -7,8 +7,55 @@ import { cyan, red, ProgressBar, clearLine } from './utils/colors.js';
7
7
  export { parseIgnoreFile };
8
8
  export { listDirectSubdirectories };
9
9
 
10
+ async function executeInDirectory(subdirPath, command, args, verbose) {
11
+ try {
12
+ if (verbose) {
13
+ console.log(`=== Executing in: ${cyan(subdirPath)} ===`);
14
+ }
15
+
16
+ let result;
17
+
18
+ await within(async () => {
19
+ cd(subdirPath);
20
+ if (verbose) {
21
+ result = await $`${command} ${args}`;
22
+ } else {
23
+ result = await $`${command} ${args}`.quiet();
24
+ }
25
+ if (verbose) {
26
+ console.log(`${cyan(subdirPath)}: `, result.stdout);
27
+ if (result.stderr) {
28
+ console.error(`${cyan(subdirPath)}: `, result.stderr);
29
+ }
30
+ }
31
+ });
32
+
33
+ return {
34
+ success: true,
35
+ stdout: result.stdout,
36
+ stderr: result.stderr
37
+ };
38
+ } catch (error) {
39
+ if (verbose) {
40
+ console.error(red(`Error in ${cyan(subdirPath)}: ${error.message}`));
41
+ if (error.stdout) {
42
+ console.log(`${cyan(subdirPath)}: `, error.stdout);
43
+ }
44
+ if (error.stderr) {
45
+ console.error(`${cyan(subdirPath)}: `, error.stderr);
46
+ }
47
+ }
48
+ return {
49
+ success: false,
50
+ error: error.message,
51
+ stdout: error.stdout || '',
52
+ stderr: error.stderr || ''
53
+ };
54
+ }
55
+ }
56
+
10
57
  export async function batchExecute(targetDir, command, args, options = {}) {
11
- const { skipPaths = [], verbose = false, showProgress = true } = options;
58
+ const { skipPaths = [], verbose = false, showProgress = true, parallel = true } = options;
12
59
 
13
60
  const absoluteTargetDir = path.resolve(targetDir);
14
61
 
@@ -22,60 +69,54 @@ export async function batchExecute(targetDir, command, args, options = {}) {
22
69
  progressBar.start();
23
70
  }
24
71
 
25
- for (let i = 0; i < subdirs.length; i++) {
26
- const subdir = subdirs[i];
27
- const subdirPath = path.join(absoluteTargetDir, subdir);
72
+ if (parallel) {
73
+ const promises = subdirs.map(async (subdir, index) => {
74
+ const subdirPath = path.join(absoluteTargetDir, subdir);
75
+ const result = await executeInDirectory(subdirPath, command, args, verbose);
28
76
 
29
- try {
30
- if (verbose) {
31
- console.log(`=== Executing in: ${cyan(subdirPath)} ===`);
77
+ if (progressBar) {
78
+ progressBar.increment();
32
79
  }
33
80
 
34
- let result;
81
+ return { directory: subdir, ...result };
82
+ });
35
83
 
36
- cd(subdirPath);
84
+ const resolvedResults = await Promise.all(promises);
37
85
 
38
- if (verbose) {
39
- result = await $`${command} ${args}`;
40
- } else {
41
- result = await $`${command} ${args}`.quiet();
42
- }
43
-
44
- results.push({
45
- directory: subdir,
46
- success: true,
47
- stdout: result.stdout,
48
- stderr: result.stderr
49
- });
50
-
51
- if (verbose) {
52
- console.log(result.stdout);
53
- if (result.stderr) {
54
- console.error(result.stderr);
55
- }
56
- }
57
- } catch (error) {
58
- results.push({
59
- directory: subdir,
60
- success: false,
61
- error: error.message,
62
- stdout: error.stdout || '',
63
- stderr: error.stderr || ''
64
- });
65
-
66
- if (verbose) {
67
- console.error(red(`Error in ${cyan(subdir)}: ${error.message}`));
68
- if (error.stdout) {
69
- console.log(error.stdout);
70
- }
71
- if (error.stderr) {
72
- console.error(error.stderr);
73
- }
86
+ for (const subdir of subdirs) {
87
+ const result = resolvedResults.find(r => r.directory === subdir);
88
+ if (result) {
89
+ results.push(result);
74
90
  }
75
91
  }
76
-
77
- if (progressBar) {
78
- progressBar.update(i + 1);
92
+ } else {
93
+ for (let i = 0; i < subdirs.length; i++) {
94
+ const subdir = subdirs[i];
95
+ const subdirPath = path.join(absoluteTargetDir, subdir);
96
+ const result = await executeInDirectory(subdirPath, command, args, verbose);
97
+
98
+ results.push({ directory: subdir, ...result });
99
+
100
+ // if (verbose) {
101
+ // if (result.success) {
102
+ // console.log(result.stdout);
103
+ // if (result.stderr) {
104
+ // console.error(result.stderr);
105
+ // }
106
+ // } else {
107
+ // console.error(red(`Error in ${cyan(subdir)}: ${result.error}`));
108
+ // if (result.stdout) {
109
+ // console.log(result.stdout);
110
+ // }
111
+ // if (result.stderr) {
112
+ // console.error(result.stderr);
113
+ // }
114
+ // }
115
+ // }
116
+
117
+ if (progressBar) {
118
+ progressBar.update(i + 1);
119
+ }
79
120
  }
80
121
  }
81
122