batch-exec-cli 1.2.0 → 1.2.2
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/README.md +4 -4
- package/package.json +7 -1
- package/.batchexecignore +0 -9
- package/CHANGELOG.md +0 -11
- package/test/cli.test.js +0 -70
- package/test/directoryLister.test.js +0 -68
- package/test/ignoreParser.test.js +0 -93
- package/test/index.test.js +0 -70
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# batch-exec
|
|
1
|
+
# batch-exec-cli
|
|
2
2
|
|
|
3
3
|
高效批量命令执行工具,能够遍历目录内所有直接子目录并执行命令。
|
|
4
4
|
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
## 安装
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm install -g batch-exec
|
|
20
|
+
npm install -g batch-exec-cli
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
或者克隆项目后本地安装:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
git clone <repository-url>
|
|
27
|
-
cd batch-exec
|
|
27
|
+
cd batch-exec-cli
|
|
28
28
|
npm install
|
|
29
29
|
npm link
|
|
30
30
|
```
|
|
@@ -128,7 +128,7 @@ temp-*
|
|
|
128
128
|
你也可以作为库使用:
|
|
129
129
|
|
|
130
130
|
```javascript
|
|
131
|
-
import { batchExecute } from 'batch-exec';
|
|
131
|
+
import { batchExecute } from 'batch-exec-cli';
|
|
132
132
|
|
|
133
133
|
const results = await batchExecute('./my-projects', 'git', ['pull'], {
|
|
134
134
|
verbose: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "batch-exec-cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Efficiently iterate through directories and execute commands with progress display",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
"test": "echo 'Tests require Node.js >= 18.0.0 with --test support'",
|
|
12
12
|
"lint": "eslint src/ test/"
|
|
13
13
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src"
|
|
16
|
+
],
|
|
14
17
|
"keywords": [
|
|
18
|
+
"batch-exec",
|
|
19
|
+
"batchexec",
|
|
20
|
+
"batch-exec-cli",
|
|
15
21
|
"batch",
|
|
16
22
|
"execute",
|
|
17
23
|
"command",
|
package/.batchexecignore
DELETED
package/CHANGELOG.md
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
|
-
|
|
5
|
-
## 1.2.0 (2026-03-01)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
### Features
|
|
9
|
-
|
|
10
|
-
* add colors highlight 240bf16
|
|
11
|
-
* add progress display by using --no-progress 320a372
|
package/test/cli.test.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
2
|
-
import assert from 'node:assert';
|
|
3
|
-
import fs from 'fs/promises';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import os from 'os';
|
|
6
|
-
import { $ } from 'zx';
|
|
7
|
-
|
|
8
|
-
describe('CLI Integration', () => {
|
|
9
|
-
let tempDir;
|
|
10
|
-
let testProjectsDir;
|
|
11
|
-
|
|
12
|
-
beforeEach(async () => {
|
|
13
|
-
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'batch-exec-cli-test-'));
|
|
14
|
-
testProjectsDir = path.join(tempDir, 'test-projects');
|
|
15
|
-
|
|
16
|
-
await fs.mkdir(testProjectsDir);
|
|
17
|
-
await fs.mkdir(path.join(testProjectsDir, 'project1'));
|
|
18
|
-
await fs.mkdir(path.join(testProjectsDir, 'project2'));
|
|
19
|
-
await fs.mkdir(path.join(testProjectsDir, 'node_modules'));
|
|
20
|
-
|
|
21
|
-
await fs.writeFile(path.join(tempDir, '.batchexecignore'), 'node_modules');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
afterEach(async () => {
|
|
25
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should show help message with --help', async () => {
|
|
29
|
-
const result = await $`node ${path.join(process.cwd(), 'src/cli.js')} --help`;
|
|
30
|
-
assert(result.stdout.includes('Usage:'));
|
|
31
|
-
assert(result.stdout.includes('batch-exec'));
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should execute command in subdirectories', async () => {
|
|
35
|
-
const result = await $`node ${path.join(process.cwd(), 'src/cli.js')} ${testProjectsDir} echo test`;
|
|
36
|
-
assert(result.stdout.includes('Summary:'));
|
|
37
|
-
assert(result.stdout.includes('Total directories: 2'));
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should respect .batchexecignore file', async () => {
|
|
41
|
-
const result = await $`node ${path.join(process.cwd(), 'src/cli.js')} ${testProjectsDir} pwd`;
|
|
42
|
-
assert(result.stdout.includes('Total directories: 2'));
|
|
43
|
-
assert(!result.stdout.includes('node_modules'));
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should work with custom ignore file using --skip', async () => {
|
|
47
|
-
const customIgnore = path.join(tempDir, 'custom-ignore');
|
|
48
|
-
await fs.writeFile(customIgnore, 'project1\nnode_modules');
|
|
49
|
-
|
|
50
|
-
const result = await $`node ${path.join(
|
|
51
|
-
process.cwd(),
|
|
52
|
-
'src/cli.js'
|
|
53
|
-
)} --skip ${customIgnore} ${testProjectsDir} pwd`;
|
|
54
|
-
assert(result.stdout.includes('Total directories: 1'));
|
|
55
|
-
assert(!result.stdout.includes('project1'));
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should show verbose output with --verbose', async () => {
|
|
59
|
-
const result = await $`node ${path.join(process.cwd(), 'src/cli.js')} --verbose ${testProjectsDir} echo hello`;
|
|
60
|
-
assert(result.stdout.includes('Target directory:'));
|
|
61
|
-
assert(result.stdout.includes('Command:'));
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('should fail with error message when missing arguments', async () => {
|
|
65
|
-
await assert.rejects($`node ${path.join(process.cwd(), 'src/cli.js')}`, error => {
|
|
66
|
-
assert(error.stderr.includes('Missing required arguments'));
|
|
67
|
-
return true;
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
});
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
2
|
-
import assert from 'node:assert';
|
|
3
|
-
import fs from 'fs/promises';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import os from 'os';
|
|
6
|
-
import { listDirectSubdirectories } from '../src/directoryLister.js';
|
|
7
|
-
|
|
8
|
-
describe('directoryLister', () => {
|
|
9
|
-
let tempDir;
|
|
10
|
-
|
|
11
|
-
beforeEach(async () => {
|
|
12
|
-
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'batch-exec-test-'));
|
|
13
|
-
|
|
14
|
-
await fs.mkdir(path.join(tempDir, 'dir1'));
|
|
15
|
-
await fs.mkdir(path.join(tempDir, 'dir2'));
|
|
16
|
-
await fs.mkdir(path.join(tempDir, 'node_modules'));
|
|
17
|
-
await fs.mkdir(path.join(tempDir, '.git'));
|
|
18
|
-
|
|
19
|
-
await fs.writeFile(path.join(tempDir, 'file1.txt'), 'content');
|
|
20
|
-
await fs.writeFile(path.join(tempDir, 'file2.js'), 'content');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
afterEach(async () => {
|
|
24
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('listDirectSubdirectories', () => {
|
|
28
|
-
it('should list only direct subdirectories', async () => {
|
|
29
|
-
const subdirs = await listDirectSubdirectories(tempDir);
|
|
30
|
-
assert.deepStrictEqual(subdirs.sort(), ['.git', 'dir1', 'dir2', 'node_modules'].sort());
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should skip directories matching patterns', async () => {
|
|
34
|
-
const subdirs = await listDirectSubdirectories(tempDir, ['node_modules', '.git']);
|
|
35
|
-
assert.deepStrictEqual(subdirs, ['dir1', 'dir2']);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should skip directories with wildcard patterns', async () => {
|
|
39
|
-
const subdirs = await listDirectSubdirectories(tempDir, ['dir*']);
|
|
40
|
-
assert.deepStrictEqual(subdirs.sort(), ['.git', 'node_modules'].sort());
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should return sorted directory names', async () => {
|
|
44
|
-
const subdirs = await listDirectSubdirectories(tempDir);
|
|
45
|
-
assert.deepStrictEqual(subdirs, subdirs.slice().sort());
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should throw error for non-existent directory', async () => {
|
|
49
|
-
await assert.rejects(
|
|
50
|
-
listDirectSubdirectories(path.join(tempDir, 'non-existent')),
|
|
51
|
-
{ message: /Directory not found/ }
|
|
52
|
-
);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should throw error for file path', async () => {
|
|
56
|
-
await assert.rejects(
|
|
57
|
-
listDirectSubdirectories(path.join(tempDir, 'file1.txt')),
|
|
58
|
-
{ message: /Not a directory/ }
|
|
59
|
-
);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should work with relative paths', async () => {
|
|
63
|
-
const relativePath = path.relative(process.cwd(), tempDir);
|
|
64
|
-
const subdirs = await listDirectSubdirectories(relativePath);
|
|
65
|
-
assert.deepStrictEqual(subdirs.sort(), ['.git', 'dir1', 'dir2', 'node_modules'].sort());
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
});
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
2
|
-
import assert from 'node:assert';
|
|
3
|
-
import fs from 'fs/promises';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import os from 'os';
|
|
6
|
-
import { parseIgnoreFile, shouldSkipDirectory } from '../src/ignoreParser.js';
|
|
7
|
-
|
|
8
|
-
describe('ignoreParser', () => {
|
|
9
|
-
let tempDir;
|
|
10
|
-
|
|
11
|
-
beforeEach(async () => {
|
|
12
|
-
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'batch-exec-test-'));
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(async () => {
|
|
16
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe('parseIgnoreFile', () => {
|
|
20
|
-
it('should parse ignore file correctly', async () => {
|
|
21
|
-
const ignoreFilePath = path.join(tempDir, '.testignore');
|
|
22
|
-
await fs.writeFile(ignoreFilePath, `
|
|
23
|
-
# This is a comment
|
|
24
|
-
node_modules
|
|
25
|
-
dist/
|
|
26
|
-
*.tmp
|
|
27
|
-
test-*
|
|
28
|
-
|
|
29
|
-
# Another comment
|
|
30
|
-
build
|
|
31
|
-
`.trim());
|
|
32
|
-
|
|
33
|
-
const patterns = await parseIgnoreFile(ignoreFilePath);
|
|
34
|
-
assert.deepStrictEqual(patterns, [
|
|
35
|
-
'node_modules',
|
|
36
|
-
'dist/',
|
|
37
|
-
'*.tmp',
|
|
38
|
-
'test-*',
|
|
39
|
-
'build'
|
|
40
|
-
]);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should return empty array for non-existent file', async () => {
|
|
44
|
-
const patterns = await parseIgnoreFile(path.join(tempDir, 'non-existent'));
|
|
45
|
-
assert.deepStrictEqual(patterns, []);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should return empty array for null or undefined', async () => {
|
|
49
|
-
const patterns1 = await parseIgnoreFile(null);
|
|
50
|
-
const patterns2 = await parseIgnoreFile(undefined);
|
|
51
|
-
assert.deepStrictEqual(patterns1, []);
|
|
52
|
-
assert.deepStrictEqual(patterns2, []);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('shouldSkipDirectory', () => {
|
|
57
|
-
it('should return false when no patterns provided', () => {
|
|
58
|
-
assert.strictEqual(shouldSkipDirectory('dir1', []), false);
|
|
59
|
-
assert.strictEqual(shouldSkipDirectory('dir1', null), false);
|
|
60
|
-
assert.strictEqual(shouldSkipDirectory('dir1', undefined), false);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should match exact directory names', () => {
|
|
64
|
-
assert.strictEqual(shouldSkipDirectory('node_modules', ['node_modules']), true);
|
|
65
|
-
assert.strictEqual(shouldSkipDirectory('dist', ['node_modules']), false);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('should match directory names with trailing slash', () => {
|
|
69
|
-
assert.strictEqual(shouldSkipDirectory('dist', ['dist/']), true);
|
|
70
|
-
assert.strictEqual(shouldSkipDirectory('node_modules', ['dist/']), false);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should match wildcard patterns', () => {
|
|
74
|
-
assert.strictEqual(shouldSkipDirectory('test-123', ['test-*']), true);
|
|
75
|
-
assert.strictEqual(shouldSkipDirectory('test-abc', ['test-*']), true);
|
|
76
|
-
assert.strictEqual(shouldSkipDirectory('other', ['test-*']), false);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should match extension wildcards', () => {
|
|
80
|
-
assert.strictEqual(shouldSkipDirectory('file.tmp', ['*.tmp']), true);
|
|
81
|
-
assert.strictEqual(shouldSkipDirectory('test.tmp', ['*.tmp']), true);
|
|
82
|
-
assert.strictEqual(shouldSkipDirectory('test.txt', ['*.tmp']), false);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('should match any of the patterns', () => {
|
|
86
|
-
const patterns = ['node_modules', 'dist/', '*.tmp'];
|
|
87
|
-
assert.strictEqual(shouldSkipDirectory('node_modules', patterns), true);
|
|
88
|
-
assert.strictEqual(shouldSkipDirectory('dist', patterns), true);
|
|
89
|
-
assert.strictEqual(shouldSkipDirectory('test.tmp', patterns), true);
|
|
90
|
-
assert.strictEqual(shouldSkipDirectory('src', patterns), false);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
});
|
package/test/index.test.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
2
|
-
import assert from 'node:assert';
|
|
3
|
-
import fs from 'fs/promises';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import os from 'os';
|
|
6
|
-
import { batchExecute } from '../src/index.js';
|
|
7
|
-
|
|
8
|
-
describe('batchExecute', () => {
|
|
9
|
-
let tempDir;
|
|
10
|
-
|
|
11
|
-
beforeEach(async () => {
|
|
12
|
-
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'batch-exec-test-'));
|
|
13
|
-
|
|
14
|
-
await fs.mkdir(path.join(tempDir, 'dir1'));
|
|
15
|
-
await fs.mkdir(path.join(tempDir, 'dir2'));
|
|
16
|
-
await fs.mkdir(path.join(tempDir, 'skip-me'));
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(async () => {
|
|
20
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should execute command in all subdirectories', async () => {
|
|
24
|
-
const results = await batchExecute(tempDir, 'pwd', []);
|
|
25
|
-
|
|
26
|
-
assert.strictEqual(results.length, 3);
|
|
27
|
-
|
|
28
|
-
results.forEach(result => {
|
|
29
|
-
assert.strictEqual(result.success, true);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should skip specified directories', async () => {
|
|
34
|
-
const results = await batchExecute(tempDir, 'pwd', [], {
|
|
35
|
-
skipPaths: ['skip-me']
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
assert.strictEqual(results.length, 2);
|
|
39
|
-
|
|
40
|
-
const dirs = results.map(r => r.directory);
|
|
41
|
-
assert.deepStrictEqual(dirs.sort(), ['dir1', 'dir2'].sort());
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should capture command output', async () => {
|
|
45
|
-
const results = await batchExecute(tempDir, 'echo', ['hello']);
|
|
46
|
-
|
|
47
|
-
results.forEach(result => {
|
|
48
|
-
assert.strictEqual(result.success, true);
|
|
49
|
-
assert(result.stdout.includes('hello'));
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should handle command failures gracefully', async () => {
|
|
54
|
-
const results = await batchExecute(tempDir, 'this-command-does-not-exist', []);
|
|
55
|
-
|
|
56
|
-
results.forEach(result => {
|
|
57
|
-
assert.strictEqual(result.success, false);
|
|
58
|
-
assert(result.error);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should work with multiple arguments', async () => {
|
|
63
|
-
const results = await batchExecute(tempDir, 'echo', ['hello', 'world']);
|
|
64
|
-
|
|
65
|
-
results.forEach(result => {
|
|
66
|
-
assert.strictEqual(result.success, true);
|
|
67
|
-
assert(result.stdout.includes('hello world'));
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
});
|