diff --git a/.eslintrc b/.eslintrc index c7d8712..5f1609a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,15 @@ { - "extends": "eslint:recommended", - "env": { - "node": true, - "es6": true - } + "extends": "eslint:recommended", + "env": { + "es6": true, + "node": true + }, + "rules": { + "quotes": ["error", "single"], + "semi": ["error", "always"], + "no-cond-assign": 0 + }, + "parserOptions": { + "ecmaVersion": 2017 + } } diff --git a/.gitignore b/.gitignore index 46b10eb..24b34eb 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ typings/ .env test/fixtures/test1 .DS_Store +package-lock.json diff --git a/.travis.yml b/.travis.yml index 46d8b6f..0877926 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ script: - npm run coverage - cat coverage/lcov.info | lcov-server --upload https://lcov-server.gabrielcsapo.com node_js: - - "6" - "8" os: - linux diff --git a/.tryitoutrc.js b/.tryitoutrc.js new file mode 100644 index 0000000..93d2469 --- /dev/null +++ b/.tryitoutrc.js @@ -0,0 +1,12 @@ +module.exports = { + title: "git-unstaged", + description: "🎭 Get all unstaged git repos in a folder", + links: { + Source: 'https://github.com/gabrielcsapo/git-unstaged', + Download: 'https://github.com/gabrielcsapo/git-unstaged/releases' + }, + icon: '', + demoImage: './example.gif', + template: 'product', + output: './docs' +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 6064611..d63bb5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ +# 1.0.0 (12/18/2017) + +- removes commander +- converts all synchronous activity to async (no more blocking io) + - increased speed `7%` [`322.030624ms -> 300.058158ms`] + # 0.3.0 (09/09/2017) -- code cleanup +- code cleanup - drops support for node@4 - updates license in package.json - updates `chalk@2.0.1` -> `chalk@2.1.0` diff --git a/README.md b/README.md index 88124a3..9185021 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ ## Installation ``` -npm install git-unstaged +npm install git-unstaged -g ``` ## Usage @@ -21,18 +21,20 @@ npm install git-unstaged ``` Usage: git-unstaged [options] -Options: +Commands: + -h, --help, help Output usage information + -v, --version, version Output the version number --h, --help output usage information --V, --version output the version number --d, --depth [value] the specified depth you want to recursively search for github repos --a, --all show all git repos and their status +Options: + -b, --baseDirectory [path] The base directory where operations should start from + -d, --depth [value] The specified depth you want to recursively search for git repos + -a, --all Show all git repos and their status ``` ## Example ``` -$git-unstaged --depth=2 +$git-unstaged --depth 2 /Users/gabrielcsapo/Documents/espyjs ## wip @@ -54,7 +56,7 @@ $git-unstaged --depth=2 > Since git allows the execution of non [builtin](https://github.com/git/git/blob/master/git.c#L528) methods another way to run this command would be ``` -$git unstaged --depth=2 +$git unstaged --depth 2 /Users/gabrielcsapo/Documents/espyjs ## wip diff --git a/bin/git-unstaged.js b/bin/git-unstaged.js new file mode 100755 index 0000000..8b3bf8d --- /dev/null +++ b/bin/git-unstaged.js @@ -0,0 +1,92 @@ +#!/usr/bin/env node + +const path = require('path'); +const chalk = require('chalk'); +const { search } = require('../lib/search'); +const log = console.log; // eslint-disable-line + +const args = process.argv.slice(2); +let program = {}; + +args.forEach((arg, i) => { + switch (arg) { + case '-v': + case '--version': + case 'version': + console.log(`v${require('../package.json').version}`); // eslint-disable-line + process.exit(0); + break; + case '-h': + case '--help': + case 'help': + console.log(`` + // eslint-disable-line + ` +Usage: git-unstaged [options] + +Commands: + -h, --help, help Output usage information + -v, --version, version Output the version number + +Options: + -b, --baseDirectory [path] The base directory where operations should start from + -d, --depth [value] The specified depth you want to recursively search for git repos + -a, --all Show all git repos and their status +`); + process.exit(0); + break; + case '-d': + case '--depth': + program['depth'] = args[i + 1]; + break; + case '-di': + case '--directory': + program['directory'] = path.resolve(process.cwd(), args[i + 1]); + break; + } +}); + +const { depth=0, all=true } = program; + +(async function() { + const output = await search(process.cwd(), depth); + + Object.keys(output).forEach((repo) => { + const lines = output[repo].split('\n'); + + if(all && lines.length <= 2) { + log(chalk.white.underline(repo)); + log(output[repo]); + } else if(lines.length > 2) { + log(chalk.white.underline(repo)); + lines.forEach((value, index) => { + if(index == 0) { + return log(chalk.white(value)); + } else { + // 'M' -> 'modified' + // 'A ' -> 'added' + // 'D' -> 'deleted' + // 'R' -> 'renamed' + // 'C' -> 'copied' + // '??' -> 'untracked' + + switch(value.substring(0, 2).trim()) { + case 'M': + return log(chalk.red(value)); + case 'A': + return log(chalk.green(value)); + case 'C': + return log(chalk.yellow(value)); + case 'R': + return log(chalk.magenta(value)); + case 'D': + return log(chalk.bgRed.white(value)); + case '??': + return log(chalk.gray(value)); + default: + return log(chalk.white(value)); + } + } + }); + } + }); +}()); diff --git a/bin/index.js b/bin/index.js deleted file mode 100755 index 593ee22..0000000 --- a/bin/index.js +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env node - -const chalk = require('chalk'); -const program = require('commander'); -const { search } = require('../lib/search'); -const log = console.log; // eslint-disable-line - -program - .version(require('../package.json').version) - .option('-d, --depth [value]', 'the specified depth you want to recursively search for github repos', 1) - .option('-a, --all', 'show all git repos and their status', false) - .parse(process.argv); - -search(process.cwd(), program.depth, (output) => { - Object.keys(output).forEach((repo) => { - const lines = output[repo].split('\n'); - - if(program.all && lines.length <= 2) { - log(chalk.white.underline(repo)); - log(output[repo]); - } else if(lines.length > 2) { - log(chalk.white.underline(repo)) - lines.forEach((value, index) => { - if(index == 0) { - return log(chalk.white(value)) - } else { - // 'M' -> 'modified' - // 'A ' -> 'added' - // 'D' -> 'deleted' - // 'R' -> 'renamed' - // 'C' -> 'copied' - // '??' -> 'untracked' - - switch(value.substring(0, 2).trim()) { - case 'M': - return log(chalk.red(value)) - case 'A': - return log(chalk.green(value)) - case 'C': - return log(chalk.yellow(value)) - case 'R': - return log(chalk.magenta(value)) - case 'D': - return log(chalk.bgRed.white(value)) - case '??': - return log(chalk.gray(value)) - default: - return log(chalk.white(value)) - } - } - }); - } - }); -}); diff --git a/docs/index.html b/docs/index.html index 272ea56..9908312 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,1990 +1,12 @@ - +
- + diff --git a/lib/search.js b/lib/search.js index 89f9f4d..cacdfe3 100644 --- a/lib/search.js +++ b/lib/search.js @@ -1,49 +1,68 @@ -const path = require('path'); const fs = require('fs'); -const { execSync } = require('child_process'); - -function getContents(directory) { - try { - const git = fs.statSync(path.resolve(directory, '.git')); - if(git.isDirectory()) { - return execSync('git status --porcelain --branch', { - cwd: path.resolve(directory) - }).toString('utf8'); - } - } catch(ex) { - return; +const path = require('path'); +const child_process = require('child_process'); +const { promisify } = require('util'); + +const exec = promisify(child_process.exec); +const readdir = promisify(fs.readdir); +const stat = promisify(fs.stat); + +async function getContents(directory) { + try { + const git = await stat(path.resolve(directory, '.git')); + if (git.isDirectory()) { + const { stdout } = await exec('git status --porcelain --branch', { + cwd: path.resolve(directory) + }); + return stdout; } + } catch (ex) { + return; + } } -function getDirectories(directory) { - return fs.readdirSync(directory) - .filter((dir) => fs.statSync(path.resolve(directory, dir)).isDirectory()) - .map((dir) => path.resolve(directory, dir)); +async function getDirectories(directory) { + try { + let found = []; + const files = await readdir(directory); + for(var i = 0; i < files.length; i++) { + const dir = await stat(path.resolve(directory, files[i])); + if(dir.isDirectory()) { + found.push(path.resolve(directory, files[i])); + } + } + return found; + } catch(ex) { + return []; + } } -function search(directory, depth, callback) { - let found = [[directory]]; - let output = {}; +async function search(directory, depth) { + let found = [[directory]]; + let output = {}; + + for (var i = 0; i <= depth; i++) { + for (var s = 0; s <= found[i].length - 1; s++) { + if (i < depth) { + found.push(await getDirectories(found[i][s])); + } + } + } - for(var i = 0; i <= depth; i++) { - found[i].forEach((dir) => { - if(i < depth) { - found.push(getDirectories(dir)); - } - }); + let all = [].concat.apply([], found); + for(var p = 0; p < all.length; p++) { + const dir = all[p]; + const contents = await getContents(path.resolve(directory, dir)); + if (contents) { + output[path.resolve(directory, dir)] = contents; } + } - [].concat.apply([], found).forEach((dir) => { - const contents = getContents(path.resolve(directory, dir)); - if(contents) { - output[path.resolve(directory, dir)] = contents; - } - }); - callback(output); + return output; } -module.exports = { - search, - getDirectories, - getContents +module.exports = { + search, + getDirectories, + getContents }; diff --git a/package.json b/package.json index a0a465e..ffbd684 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "git-unstaged", - "version": "0.3.0", + "version": "1.0.0", "description": "🎭 Get all unstaged git repos in a folder", "author": "Gabriel J. Csapo ", "license": "MIT", @@ -17,22 +17,24 @@ ], "main": "index.js", "bin": { - "git-unstaged": "./bin/index.js" + "git-unstaged": "./bin/git-unstaged.js" + }, + "engines": { + "node": ">= 8" }, "scripts": { "lint": "eslint .", "test": "tape test/*.js", "coverage": "tap test/*.js --coverage --coverage-report=lcov", - "generate-docs": "tryitout --output=docs --template=product" + "generate-docs": "tryitout" }, "dependencies": { - "chalk": "^2.1.0", - "commander": "^2.11.0" + "chalk": "^2.3.0" }, "devDependencies": { - "eslint": "^4.6.1", - "tap": "^10.7.2", + "eslint": "^4.13.1", + "tap": "^11.0.0", "tape": "^4.8.0", - "tryitout": "^0.2.3" + "tryitout": "^1.2.2" } } diff --git a/test/index.js b/test/index.js index b3025b6..eb5ae3f 100644 --- a/test/index.js +++ b/test/index.js @@ -1,46 +1,43 @@ const test = require('tape'); const path = require('path'); -const exec = require('child_process').exec; +const { exec } = require('child_process'); const { search, getDirectories, getContents } = require('../lib/search'); const fixturesDirectory = path.resolve(__dirname, 'fixtures'); test('git-unstaged', (t) => { - t.plan(3); + t.plan(3); - t.test('runs search in new directory', (t) => { - exec('mkdir test1 && cd test1 && git init && cat "hello world" > t.txt', { - cwd: fixturesDirectory - }); - setTimeout(() => { - search(fixturesDirectory, 1, (c) => { - t.equal(c[path.resolve(fixturesDirectory, 'test1')], "## No commits yet on master\n?? t.txt\n"); - exec('rm -r ' + path.resolve(__dirname, 'fixtures', 'test1'), () => { - t.end(); - }); - }); - }, 1000); + t.test('runs search in new directory', (t) => { + exec('mkdir test1 && cd test1 && git init && cat "hello world" > t.txt && sleep 1000', { + cwd: fixturesDirectory + }, async() => { + const output = await search(fixturesDirectory, 1); + t.equal(output[path.resolve(fixturesDirectory, 'test1')], '## No commits yet on master\n?? t.txt\n'); + exec('rm -r ' + path.resolve(__dirname, 'fixtures', 'test1'), () => { + t.end(); + }); }); + }); - t.test('runs getContents in new directory', (t) => { - exec('mkdir test1 && cd test1 && git init && cat "hello world" > t.txt', { - cwd: fixturesDirectory - }); - setTimeout(() => { - const contents = getContents(path.resolve(fixturesDirectory, 'test1')); - t.equal(contents, '## No commits yet on master\n?? t.txt\n') - exec('rm -r ' + path.resolve(__dirname, 'fixtures', 'test1'), () => { - t.end(); - }); - }, 1000); + t.test('runs getContents in new directory', (t) => { + exec('mkdir test1 && cd test1 && git init && cat "hello world" > t.txt && sleep 1000', { + cwd: fixturesDirectory + }, async() => { + const contents = await getContents(path.resolve(fixturesDirectory, 'test1')); + t.equal(contents, '## No commits yet on master\n?? t.txt\n'); + exec('rm -r ' + path.resolve(__dirname, 'fixtures', 'test1'), () => { + t.end(); + }); }); + }); - t.test('return fixtures directory', (t) => { - const directories = getDirectories(__dirname); - t.equal(directories.length, 1); - t.equal(directories[0].substring(directories[0].lastIndexOf('/') + 1, directories[0].length), 'fixtures'); - t.end(); - }); + t.test('return fixtures directory', async(t) => { + const directories = await getDirectories(__dirname); + t.equal(directories.length, 1); + t.equal(directories[0].substring(directories[0].lastIndexOf('/') + 1, directories[0].length), 'fixtures'); + t.end(); + }); }); diff --git a/tryitout.js b/tryitout.js deleted file mode 100644 index 4a68169..0000000 --- a/tryitout.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - title: "git-unstaged", - description: "🎭 Get all unstaged git repos in a folder", - sourceCodeLink: 'https://github.com/gabrielcsapo/git-unstaged', - downloadLink: 'https://github.com/gabrielcsapo/git-unstaged/releases', - icon: '', - demoImage: './example.gif' -}