Skip to content

Commit

Permalink
Add log level feature
Browse files Browse the repository at this point in the history
  • Loading branch information
zerodevx committed Mar 10, 2022
1 parent 0568f22 commit 23f60de
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 41 deletions.
47 changes: 26 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
Elegant replacement for [`each-cli`](https://www.npmjs.com/package/each-cli),
[`foreach-cli`](https://www.npmjs.com/package/foreach-cli) and \*nix
[`find -exec`](https://man7.org/linux/man-pages/man1/find.1.html) command. Very useful for NPM
[`find -exec`](https://man7.org/linux/man-pages/man1/find.1.html) command in
very little lines of
[code](https://github.com/zerodevx/findx-cli/blob/main/cli.js). Useful for NPM
scripts or CI.

- [x] Cross-platform
- [x] Concurrency support
- [x] Continues on error, then exits with code 1
- [x] Logs output of every execution
- [x] Displays progress indicator when TTY
- [x] Continue on error, then exit with code 1
- [x] Control output of every execution
- [x] Task progress indication in TTY

## Install

Expand All @@ -25,8 +27,8 @@ $ npm i -g findx-cli
$ findx '**/*.jpg' -- convert {{path}} {{dir}}/{{name}}.png
```

This searches for all files matching the glob pattern, then runs the provided command against each
match.
This searches for all files matching the glob pattern, then runs the provided
command against each match.

See below for more usage [examples](#examples).

Expand All @@ -43,38 +45,40 @@ Arguments:
Options:
-C, --concurrent <max> concurrent number of executions (default: 10)
-S, --shell run each execution in new shell
-d, --cd change to path directory for each run
--log <level> log level (choices: "stdout", "stderr", "all",
"none", default: "all")
--sh run each execution in new shell
--cd change to path directory for each run
-V, --version output the version number
-h, --help display help for command
```

## Command templating

Write your command using [mustache](https://github.com/janl/mustache.js/) syntax. The following tags
are available:
Write your command using [mustache](https://github.com/janl/mustache.js/)
syntax. The following tags are available:

| Tag | Example | Description |
| -------- | ----------------------- | ------------------------ |
| {{path}} | /home/user/dir/file.txt | Full path of file |
| {{root}} | / | Root |
| {{dir}} | /home/user/dir | Directory portion |
| {{base}} | file.txt | File name with extension |
| {{name}} | file | Name portion |
| {{ext}} | .txt | Extension portion |
| Tag | Eg | Desc |
| ---------- | ----------------------- | ------------------------ |
| `{{path}}` | /home/user/dir/file.txt | Full path of file |
| `{{root}}` | / | Root |
| `{{dir}}` | /home/user/dir | Directory portion |
| `{{base}}` | file.txt | File name with extension |
| `{{name}}` | file | Name portion |
| `{{ext}}` | .txt | Extension portion |

## Examples

#### Untar each tar file in its own directory

```
$ findx '**/*.tar' -d -- tar -xvf {{base}}
$ findx '**/*.tar' --cd -- tar -xvf {{base}}
```

#### Ignore some files and run shell-specific commands

```
$ findx '**/LICENSE !ignored/**' -S -- 'cd {{dir}} && cat LICENSE'
$ findx '**/LICENSE !ignored/**' --sh -- 'cd {{dir}} && cat LICENSE'
```

#### Dry-run glob matches
Expand All @@ -85,7 +89,8 @@ $ findx '**/*.@(txt,xml)'

## Development

Standard Github [contribution workflow](https://github.com/firstcontributions/first-contributions)
Standard Github
[contribution workflow](https://github.com/firstcontributions/first-contributions)
applies.

#### Tests
Expand Down
44 changes: 28 additions & 16 deletions cli.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/usr/bin/env node
import { program } from 'commander'
import { program, Option } from 'commander'
import { execaCommand } from 'execa'
import fastglob from 'fast-glob'
import mustache from 'mustache'
import tasuku from 'tasuku'
import chalk from 'chalk'
import pmap from 'p-map'
import fs from 'node:fs/promises'
import path from 'node:path'
Expand All @@ -20,8 +21,13 @@ program
.argument('<globs>', 'globs to match')
.argument('[commands...]', 'commands to execute')
.option('-C, --concurrent <max>', 'concurrent number of executions', 10)
.option('-S, --shell', 'run each execution in new shell')
.option('-d, --cd', 'change to path directory for each run')
.addOption(
new Option('--log <level>', 'log level')
.choices(['stdout', 'stderr', 'all', 'none'])
.default('all')
)
.option('--sh', 'run each execution in new shell')
.option('--cd', 'change to path directory for each run')
.version(version)
.parse()

Expand All @@ -43,30 +49,36 @@ if (!cmd) {
process.exit()
}

let errflag
mustache.escape = (noop) => noop

const run = (match) =>
execaCommand(
const exe = async (match) => {
const spawn = await execaCommand(
mustache.render(cmd, {
path: match,
...path.parse(match)
}),
{
all: true,
reject: false,
...(opts.shell && { shell: true }),
...(opts.sh && { shell: true }),
...(opts.cd && { cwd: path.dirname(match) })
}
).then(({ exitCode, command, all }) => {
if (exitCode) errflag = true
console.log(`${exitCode ? '✖' : '✔'} ${command}\n${all}`)
})
)
const { exitCode: err, escapedCommand: esc, [opts.log]: log } = spawn
const { red: r, green: g, gray: y } = chalk
console.log(`${err ? r('✖') : g('✔')} ${y(esc)}${log ? `\n${log}` : ''}`)
return err
}

const runtty = (match) => tasuku(match, () => run(match)).then(({ clear }) => clear())
let count = 0
const exetty = async (match) => {
const task = await tasuku(`[${++count}/${matches.length}] ${match}`, () =>
exe(match)
)
task.clear()
return task.result
}

await pmap(matches, process.stdout.isTTY ? runtty : run, {
const run = await pmap(matches, process.stdout.isTTY ? exetty : exe, {
concurrency: opts.concurrent
})

if (errflag) process.exit(1)
if (run.some((r) => r)) process.exit(1)
11 changes: 8 additions & 3 deletions test/helpers/mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@ switch (args[0]) {
}
break
}
case 'logs': {
console.log('test-stdout')
throw new Error('test-stderr')
}
default: {
const r = rand(1, 10)
await sleep(r * 1000)
console.log(r)
const t = rand(1, 10)
await sleep(t * 1000)
if (!rand(0, 5)) throw new Error(`Random error: ${t}s`)
console.log(`Mock took: ${t}s`)
}
}
31 changes: 30 additions & 1 deletion test/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,36 @@ test('concurrency', (t) => {

test('error', (t) => {
const err = t.throws(() =>
run('-C', '2', 'test/**/*.txt', 'node', 'test/helpers/mock.js', 'error', '{{dir}}')
run(
'-C',
'2',
'test/**/*.txt',
'node',
'test/helpers/mock.js',
'error',
'{{dir}}'
)
)
t.is(err.stdout.split('done').length, 5)
})

test('logs', (t) => {
const run2 = (...args) =>
t.throws(() =>
run(
...args,
'test/fixtures/dummy.txt',
'node',
'test/helpers/mock.js',
'logs'
)
)
const { stdout: all } = run2()
t.true(all.includes('test-stdout') && all.includes('test-stderr'))
const { stdout } = run2('--log', 'stdout')
t.true(stdout.includes('test-stdout') && !stdout.includes('test-stderr'))
const { stdout: stderr } = run2('--log', 'stderr')
t.true(!stderr.includes('test-stdout') && stderr.includes('test-stderr'))
const { stdout: none } = run2('--log', 'none')
t.true(!none.includes('test-stdout') && !none.includes('test-stderr'))
})

0 comments on commit 23f60de

Please sign in to comment.