UNPKG

recursive-exec

Version:

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

113 lines (111 loc) 5.23 kB
//! recursive-exec v1.1.8 ~~ https://github.com/center-key/recursive-exec ~~ MIT License import { cliArgvUtil } from 'cli-argv-util'; import { globSync } from 'glob'; import { spawnSync } from 'node:child_process'; import chalk from 'chalk'; import fs from 'node:fs'; import log from 'fancy-log'; import path from 'node:path'; import slash from 'slash'; const recursiveExec = { version: '1.1.8', assertOk(ok, message) { if (!ok) throw new Error(`[recursive-exec] ${message}`); }, find(folder, command, options) { const defaults = { echo: false, excludes: null, extensions: null, quiet: false, }; const settings = { ...defaults, ...options }; const error = !folder ? 'Must specify the folder path.' : !fs.existsSync(folder) ? 'Folder does not exist: ' + folder : !fs.statSync(folder).isDirectory() ? 'Folder is not a folder: ' + folder : !command ? 'Command template missing.' : null; recursiveExec.assertOk(!error, error); const startTime = Date.now(); const source = slash(path.normalize(folder)).replace(/\/$/, ''); const getExts = () => settings.extensions.join('|'); const extensions = !settings.extensions ? '' : `@(${getExts()})`; const globOptions = { ignore: '**/node_modules/**/*', nodir: true }; const filesRaw = globSync(source + '/**/*' + extensions, globOptions).map(slash); const excludes = settings.excludes || []; const keep = (file) => !excludes.find(exclude => file.includes(exclude)); const files = filesRaw.filter(keep).sort(); const toCamel = (token) => token.replace(/-./g, char => char[1].toUpperCase()); const calcResult = (file) => { const parts = path.parse(file); const filename = file.substring(source.length + 1); const relative = parts.dir.substring(source.length + 1); const basename = filename.substring(0, filename.length - path.extname(filename).length); const interpolate = (template) => template .replaceAll('{{file}}', file) .replaceAll('{{filename}}', filename) .replaceAll('{{basename}}', basename) .replaceAll('{{path}}', relative) .replaceAll('{{name}}', parts.name) .replaceAll('{{nameCamelCase}}', toCamel(parts.name)); return { folder: source, file: file, path: relative, filename: filename, basename: basename, name: parts.name, command: interpolate(command), }; }; const results = files.map(calcResult); const name = chalk.gray('recursive-exec'); const version = chalk.gray('v' + recursiveExec.version); const summary = chalk.white(`(files: ${files.length})`); const dryRunNote = settings.echo ? chalk.yellow('[dry run]') : ''; if (!settings.quiet) log(name, version, source, summary, dryRunNote); const execCommand = (result, index) => { const count = chalk.magenta(index + 1); if (!settings.quiet || settings.echo) log(name, count, chalk.white(result.command), dryRunNote); const exec = () => { const task = spawnSync(result.command, { shell: true, stdio: 'inherit' }); const errorMsg = () => `Status: ${task.status}, Command: ${result.command}`; recursiveExec.assertOk(task.status === 0, errorMsg()); }; if (!settings.echo) exec(); }; results.forEach(execCommand); const duration = chalk.white(`(${Date.now() - startTime}ms)`); if (!settings.quiet) log(name, chalk.green('done'), duration); return results; }, cli() { const validFlags = ['echo', 'exclude', 'ext', 'note', 'quiet']; const cli = cliArgvUtil.parse(validFlags); const folder = cli.params[0]; const command = cli.params[1]; const macroName = command?.match(/^{{command:(.*)}}$/)?.[1]; const readPkg = () => JSON.parse(fs.readFileSync('package.json', 'utf-8')); const macroValue = macroName && readPkg().recursiveExecConfig?.commands?.[macroName]; const error = cli.invalidFlag ? cli.invalidFlagMsg : !folder ? 'Missing source folder.' : !command ? 'Missing command to execute.' : cli.paramCount > 2 ? 'Extraneous parameter: ' + cli.params[2] : macroName && !macroValue ? 'Command macro not defined: ' + macroName : null; recursiveExec.assertOk(!error, error); const options = { echo: cli.flagOn.echo, excludes: cli.flagMap.exclude?.split(',') ?? null, quiet: cli.flagOn.quiet, extensions: cli.flagMap.ext?.split(',') ?? null, }; recursiveExec.find(folder, macroValue ?? command, options); }, }; export { recursiveExec };