Skip to content

Commit

Permalink
Merge pull request #24 from facebook/dir-resolver
Browse files Browse the repository at this point in the history
Add ability to pass one or many directories to jscodeshift. Fixes #21
  • Loading branch information
cpojer committed Jul 28, 2015
2 parents b59b50f + d77b115 commit cc4a8c5
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 98 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@ $ jscodeshift --help
Usage: jscodeshift <path>... [options]
path Files to transform
path Files or directory to transform
Options:
-t FILE, --transform FILE Path to the transform file [./transform.js]
-c, --cpus (all by default) Determines the number of processes started.
-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
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 5 additions & 5 deletions bin/__tests__/jscodeshift-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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');
}
Expand All @@ -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');
}
Expand Down
6 changes: 5 additions & 1 deletion bin/jscodeshift.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
202 changes: 112 additions & 90 deletions src/Runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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',
Expand All @@ -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;

0 comments on commit cc4a8c5

Please sign in to comment.