diff --git a/README.md b/README.md index 2726318b..4b4ce65f 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ $ jscodeshift --help Usage: jscodeshift ... [options] -path Files to transform +path Files or directory to transform Options: -t FILE, --transform FILE Path to the transform file [./transform.js] @@ -36,6 +36,8 @@ Options: -v, --verbose Show more information about the transform process [0] -d, --dry Dry run (no changes are made to files) -p, --print Print output, useful for development + --babel Do not apply babel for transform files [true] + --extensions File extensions the transform file should be applied to [js] ``` This passes the source of all passed through the transform module specified @@ -133,7 +135,7 @@ You can collect even more stats via the `stats` function as explained above. ### Example ```text -$ jscodeshift -t myTransform.js src/**/*.js +$ jscodeshift -t myTransform.js src Processing 10 files... Spawning 2 workers with 5 files each... All workers done. diff --git a/bin/__tests__/jscodeshift-test.js b/bin/__tests__/jscodeshift-test.js index 183bb21d..af22565c 100644 --- a/bin/__tests__/jscodeshift-test.js +++ b/bin/__tests__/jscodeshift-test.js @@ -15,7 +15,7 @@ jest.autoMockOff(); // Increase default timeout (5000ms) for Travis -jasmine.getEnv().defaultTimeoutInterval = 10000; +jasmine.getEnv().defaultTimeoutInterval = 15000; var child_process = require('child_process'); var fs = require('fs'); @@ -67,13 +67,13 @@ describe('jscodeshift CLI', () => { ); return Promise.all([ - run(['-t', transformA, sourceA, sourceB]).then( + run(['--no-extensions', '-t', transformA, sourceA, sourceB]).then( ([stdout, stderr]) => { expect(fs.readFileSync(sourceA).toString()).toBe('transforma'); expect(fs.readFileSync(sourceB).toString()).toBe('transformb'); } ), - run(['-t', transformB, sourceC]).then( + run(['--no-extensions', '-t', transformB, sourceC]).then( ([stdout, stderr]) => { expect(fs.readFileSync(sourceC).toString()).toBe(sourceC); } @@ -101,7 +101,7 @@ describe('jscodeshift CLI', () => { ' typeof api.stats === "function"', ' );', ].join('\n')); - return run(['-t', transform, source]).then( + return run(['--no-extensions', '-t', transform, source]).then( ([stdout, stderr]) => { expect(fs.readFileSync(source).toString()).toBe('true'); } @@ -111,7 +111,7 @@ describe('jscodeshift CLI', () => { pit('passes options along to the transform', () => { var source = createTempFileWith('a'); var transform = createTransformWith('return options.foo;'); - return run(['-t', transform, '--foo=42', source]).then( + return run(['--no-extensions', '-t', transform, '--foo=42', source]).then( ([stdout, stderr]) => { expect(fs.readFileSync(source).toString()).toBe('42'); } diff --git a/bin/jscodeshift.sh b/bin/jscodeshift.sh index b461481f..d3a0e7d1 100755 --- a/bin/jscodeshift.sh +++ b/bin/jscodeshift.sh @@ -19,7 +19,7 @@ var opts = require('nomnom') .options({ path: { position: 0, - help: 'Files to transform', + help: 'Files or directory to transform', list: true, metavar: 'FILE', required: true @@ -54,6 +54,10 @@ var opts = require('nomnom') flag: true, default: true, help: 'Do not apply babel for transform files' + }, + extensions: { + default: 'js', + help: 'File extensions the transform file should be applied to' } }) .parse(); diff --git a/package.json b/package.json index b80eed6f..57138672 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "babel-jest": "^5.3.0", "es6-promise": "^2.0.1", "jest-cli": "^0.4.0", + "node-dir": "0.1.8", "temp": "^0.8.1" }, "jest": { diff --git a/src/Runner.js b/src/Runner.js index efe2d934..6b445983 100644 --- a/src/Runner.js +++ b/src/Runner.js @@ -10,23 +10,30 @@ 'use strict'; -var child_process = require('child_process'); -var clc = require('cli-color'); -var cpus = require('os').cpus().length - 1; -var fs = require('fs'); +require('es6-promise').polyfill(); -function ok(msg, verbose) { - verbose >= 2 && console.log(clc.white.bgGreen(' OKK '), msg); -} -function nochange(msg, verbose) { - verbose >= 1 && console.log(clc.white.bgYellow(' NOC '), msg); -} -function skip(msg, verbose) { - verbose >= 1 && console.log(clc.white.bgYellow(' SKIP'), msg); -} -function error(msg, verbose) { - verbose >= 0 && console.log(clc.white.bgRedBright(' ERR '), msg); -} +const child_process = require('child_process'); +const clc = require('cli-color'); +const dir = require('node-dir'); +const fs = require('fs'); +const path = require('path'); + +const availableCpus = require('os').cpus().length - 1; + +const log = { + ok(msg, verbose) { + verbose >= 2 && console.log(clc.white.bgGreen(' OKK '), msg); + }, + nochange(msg, verbose) { + verbose >= 1 && console.log(clc.white.bgYellow(' NOC '), msg); + }, + skip(msg, verbose) { + verbose >= 1 && console.log(clc.white.bgYellow(' SKIP'), msg); + }, + error(msg, verbose) { + verbose >= 0 && console.log(clc.white.bgRedBright(' ERR '), msg); + }, +}; function showFileStats(fileStats) { console.log( @@ -39,23 +46,42 @@ function showFileStats(fileStats) { } function showStats(stats) { - var names = Object.keys(stats).sort(); + const names = Object.keys(stats).sort(); if (names.length) { console.log(clc.blue('Stats:')); } - names.forEach(function(name) { - console.log(name + ':', stats[name]); - }); + names.forEach(name => console.log(name + ':', stats[name])); } -var log = { - 'ok': ok, - 'nochange': nochange, - 'skip': skip, - 'error': error -}; +function getAllFiles(paths) { + return Promise.all( + paths.map(file => new Promise((resolve, reject) => { + fs.lstat(file, (err, stat) => { + if (err) { + console.log('Skipping path "%s" which does not exist.', file); + resolve(); + return; + } + + if (stat.isDirectory()) { + dir.files(file, (err, list) => resolve(list)); + } else { + resolve([file]); + } + }) + })) + ).then(files => [].concat(...files)); +} + +function run(transformFile, paths, options) { + const cpus = options.cpus ? Math.min(cpus, options.cpus) : availableCpus; + const extensions = + options.extensions && options.extensions.split(',').map(ext => '.' + ext); + const fileChunks = []; + const fileCounters = {error: 0, ok: 0, nochange: 0, skip: 0}; + const statsCounter = {}; + const startTime = process.hrtime(); -function run(transformFile, files, options) { if (!fs.existsSync(transformFile)) { console.log( clc.whiteBright.bgRed('ERROR') + ' Transform file %s does not exist', @@ -64,75 +90,71 @@ function run(transformFile, files, options) { return; } - if (files.length === 0) { - console.log('No files selected, nothing to do.'); - return; - } - - if (options.cpus) { - cpus = Math.min(cpus, options.cpus); - } - var processes = Math.min(files.length, cpus); - var chunk_size = Math.ceil(files.length / processes); - var file_chunks = []; - for (var i = 0, l = files.length; i < l; i += chunk_size) { - file_chunks.push(files.slice(i, i + chunk_size)); - } + getAllFiles(paths) + .then(files => files.filter( + name => !extensions || extensions.indexOf(path.extname(name)) != -1 + )) + .then(files => { + if (files.length === 0) { + console.log('No files selected, nothing to do.'); + return; + } - console.log('Processing %d files...', files.length); - console.log( - 'Spawning %d workers with %d files each...', - file_chunks.length, - file_chunks[0].length - ); - if (options.dry) { - console.log(clc.green('Running in dry mode, no files be written!')); - } + const processes = Math.min(files.length, cpus); + const chunkSize = Math.ceil(files.length / processes); + for (let i = 0, l = files.length; i < l; i += chunkSize) { + fileChunks.push(files.slice(i, i + chunkSize)); + } - var fileCounters = {error: 0, ok: 0, nochange: 0, skip: 0}; - var statsCounter = {}; - var doneCounter = 0; - - function onEnd() { - doneCounter += 1; - if (doneCounter === file_chunks.length) { - var endTime = process.hrtime(startTime); - console.log('All workers done.'); - showFileStats(fileCounters); - showStats(statsCounter); + console.log('Processing %d files...', files.length); console.log( - 'Time elapsed: %d.%d seconds', - endTime[0], - (endTime[1]/1000000).toFixed(0) + 'Spawning %d workers with %d files each...', + fileChunks.length, + fileChunks[0].length ); - } - } - - function onMessage(message) { - switch (message.action) { - case 'status': - fileCounters[message.status] += 1; - log[message.status](message.msg, options.verbose); - break; - case 'update': - if (!statsCounter[message.name]) { - statsCounter[message.name] = 0; - } - statsCounter[message.name] += message.quantity; - break; - } - } + if (options.dry) { + console.log( + clc.green('Running in dry mode, no files will be written!') + ); + } - var startTime = process.hrtime(); - file_chunks.forEach(function(files) { - var child = child_process.fork( - require.resolve('./Worker'), - [transformFile, options.babel ? 'babel' : 'no-babel'] + return fileChunks.map(files => { + const child = child_process.fork( + require.resolve('./Worker'), + [transformFile, options.babel ? 'babel' : 'no-babel'] + ); + child.send({files, options}); + child.on('message', message => { + switch (message.action) { + case 'status': + fileCounters[message.status] += 1; + log[message.status](message.msg, options.verbose); + break; + case 'update': + if (!statsCounter[message.name]) { + statsCounter[message.name] = 0; + } + statsCounter[message.name] += message.quantity; + break; + } + }); + return new Promise(resolve => child.on('disconnect', resolve)); + }); + }) + .then(pendingWorkers => + Promise.all(pendingWorkers).then(resolve => { + const endTime = process.hrtime(startTime); + console.log('All workers done.'); + showFileStats(fileCounters); + showStats(statsCounter); + console.log( + 'Time elapsed: %d.%d seconds', + endTime[0], + (endTime[1]/1000000).toFixed(0) + ); + resolve(); + }) ); - child.send({files: files, options: options}); - child.on('message', onMessage); - child.on('disconnect', onEnd); - }); } exports.run = run;