# recursive-exec
<img src=https://centerkey.com/graphics/center-key-logo.svg align=right width=200 alt=logo>

_Run a command on each file in a folder and its subfolders (CLI tool designed for use in npm package.json scripts)_

[![License:MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/center-key/recursive-exec/blob/main/LICENSE.txt)
[![npm](https://img.shields.io/npm/v/recursive-exec.svg)](https://www.npmjs.com/package/recursive-exec)
[![Build](https://github.com/center-key/recursive-exec/actions/workflows/run-spec-on-push.yaml/badge.svg)](https://github.com/center-key/recursive-exec/actions/workflows/run-spec-on-push.yaml)

**recursive-exec** is the Unix `find -type f -exec` command for use in your project's **package.json** file.

<img src=https://raw.githubusercontent.com/center-key/recursive-exec/main/screenshot.png
width=800 alt=screenshot>

## A) Setup
Install package for node:
```shell
$ npm install --save-dev recursive-exec
```

## B) Usage
### 1. Synopsis
```
recursive-exec [PATH] [COMMAND]
```
Parameters:
* The **first** parameter is the *source* folder.
* The **second** parameter is the *command template* string.

### 2. npm package.json scripts
Run `recursive-exec` from the `"scripts"` section of your **package.json** file.

Example **package.json** scripts:
```json
"scripts": {
   "minimize-js": "recursive-exec build/web --ext=.js 'uglifyjs {{file}} --output dist/web/{{basename}}.min.js'"
},
```

The command template supports 6 variables:
| Template Variable   | Description                                   | Example (source: `'build/web'`)  |
| ------------------- | --------------------------------------------- | -------------------------------- |
| `{{file}}`          | Full path including filename.                 | `'build/web/lib/fetch-json.js'`  |
| `{{filename}}`      | Relative path including filename.             | `'lib/fetch-json.js'`            |
| `{{basename}}`      | Relative path including filename<br>without file extension. | `'lib/fetch-json'` |
| `{{path}}`          | Relative path without filename.               | `'lib'`                          |
| `{{name}}`          | Basename of file.                             | `'fetch-json'`                   |
| `{{nameCamelCase}}` | Basename of file converted to camel case.     | `'fetchJson'`                    |

### 3. Command macros
For reusability and readability, best practice is to define commands as macros in your project's **package.json** file.

Example **package.json** scripts:
```json
"recursiveExecConfig": {
   "commands": {
      "make-min-file": "uglifyjs {{file}} --output dist/web/{{basename}}.min.js"
   }
},
"scripts": {
   "minimize-js": "recursive-exec build/web --ext=.js {{command:make-min-file}}"
},
```

### 4. Command-line npx
Example terminal command to minimize JavaScript files:
```shell
$ npm install --save-dev recursive-exec
$ npx recursive-exec build/web --ext=.js "uglifyjs {{file}} --output dist/web/{{basename}}.min.js"
```
You can also install **recursive-exec** globally (`--global`) and then run it anywhere directly from the terminal.

### 5. CLI flags
Command-line flags:
| Flag         | Description                                                | Value      |
| ------------ | ---------------------------------------------------------- | ---------- |
| `--echo`     | Show dry run preview of each command without executig it.  | N/A        |
| `--exclude`  | Comma separated list of strings to match in paths to skip. | **string** |
| `--ext`      | Filter files by file extension, such as `.js`.<br>Use a comma to specify multiple extensions. | **string** |
| `--note`     | Place to add a comment only for humans.                    | **string** |
| `--quiet`    | Suppress informational messages.                           | N/A        |

### 6. Examples
   - `recursive-exec src/web --ext=.less 'lessc src/web/{{filename}} build/web/{{basename}}.css'`<br>
   Compiles all LESS files in the **src/web** folder into CSS files in the **build/web** folder.

   - `recursive-exec src/web --ext=.less {{command:compile-less}}`<br>
   Identical to the previous example if a command macro named `compile-less` is defined correctly in your project's **package.json** file.

   - `recursive-exec src/web --ext=.less 'lessc {{file}} build/web/{{basename}}.css'`<br>
   Identical to the previous example since `{{file}}` includes the **source** folder (`src/web`) in the path.

   - `recursive-exec build/web --ext=.css 'csso {{file}} --output dist/web/{{filename}}'`<br>
   Optimizes the CSS files in the **build/web** folder and save the new files to the **dist/web** folder.

   - `recursive-exec build/web --ext=.js --quiet 'make-dir dist/web/{{path}}'`<br>
   Duplicates the folder structure from **build/web** over to **dist/web** (first run `npm install --save-dev make-dir-cli`).

   - `recursive-exec build/web --ext=.js 'uglifyjs {{file}} --output dist/web/{{basename}}.min.js'`<br>
   Minimizes the JavaScript files in the **build/web** folder and saves the new files to the **dist/web** folder with the **.min.js** file extension.

   - `recursive-exec src 'glob {{file}}'`<br>
   Lists out all source files.

   - `recursive-exec build/web-app --ext=.js --exclude=modules 'rollup {{file}} --file dist/web-app/{{filename}} --name {{nameCamelCase}}'`<br>
   Uses **rollup** to bundle the JavaScript for each web page but skip over the **modules** folders.

> [!NOTE]
> _Single quotes in commands are normalized so they work cross-platform and avoid the errors often encountered on Microsoft Windows._

## C) Application Code
Even though **recursive-exec** is primarily intended for build scripts, the package can be used programmatically in ESM and TypeScript projects.

Example:
``` typescript
import { recursiveExec } from 'recursive-exec';

const options = { quiet: true };
const results = recursiveExec.find('src/web', 'ls -o {{file}}', options);
console.info('Number of files:', results.length);
```

See the **TypeScript Declarations** at the top of [recursive-exec.ts](src/recursive-exec.ts) for documentation.

<br>

---
[MIT License](LICENSE.txt)

[🛡️ npm Security Aggregator](https://center-key.github.io/npm-security-aggregator/?package=recursive-exec)

See the `runScriptsConfig` section of [`package.json`](package.json) for a clean way to organize build tasks:
   - 🎋 [`add-dist-header`](https://github.com/center-key/add-dist-header) &mdash;&nbsp; _Prepend a one-line banner comment (with license notice) to distribution files_
   - 📄 [`copy-file-util`](https://github.com/center-key/copy-file-util) &mdash;&nbsp; _Copy or rename a file with optional package version number_
   - 📂 [`copy-folder-util`](https://github.com/center-key/copy-folder-util) &mdash;&nbsp; _Recursively copy files from one folder to another folder_
   - 🪺 [`recursive-exec`](https://github.com/center-key/recursive-exec) &mdash;&nbsp; _Run a command on each file in a folder and its subfolders_
   - 🔍 [`replacer-util`](https://github.com/center-key/replacer-util) &mdash;&nbsp; _Find and replace strings or template outputs in text files_
   - 🔢 [`rev-web-assets`](https://github.com/center-key/rev-web-assets) &mdash;&nbsp; _Revision web asset filenames with cache busting content hash fingerprints_
   - 🚆 [`run-scripts-util`](https://github.com/center-key/run-scripts-util) &mdash;&nbsp; _Organize npm package.json scripts into groups of easy-to-manage commands_
   - 🚦 [`w3c-html-validator`](https://github.com/center-key/w3c-html-validator) &mdash;&nbsp; _Check the markup validity of HTML files using the W3C validator_
