diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..cef23f00 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +*.log +node_modules +build +*.node +components +coverage +*.orig +.idea +sandbox +test/out-fixtures/* +test/watch-*.txt +gulp.1 diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 00000000..af7124ee --- /dev/null +++ b/.jshintrc @@ -0,0 +1,19 @@ +{ + "camelcase": true, + "curly": true, + "eqeqeq": true, + "freeze": true, + "indent": 2, + "newcap": false, + "quotmark": "single", + "maxdepth": 3, + "maxstatements": 50, + "maxlen": 80, + "eqnull": true, + "funcscope": true, + "strict": true, + "undef": true, + "unused": true, + "node": true, + "mocha": true +} \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..f93c3951 --- /dev/null +++ b/.npmignore @@ -0,0 +1,12 @@ +.DS_Store +*.log +node_modules +build +*.node +components +coverage +*.orig +.idea +sandbox +test/out-fixtures/* +test/watch-*.txt diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..dcbee380 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: node_js +node_js: + - "0.10" + - "0.11" +after_script: + - npm run coveralls +git: + depth: 10 diff --git a/README.md b/README.md new file mode 100644 index 00000000..bc29bea5 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +

+ + + +

+ +# gulp-cli [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Support us][gittip-image]][gittip-url] [![Build Status][travis-image]][travis-url] [![Coveralls Status][coveralls-image]][coveralls-url] +> The streaming build system + +[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/wearefractal/gulp/trend.png)](https://bitdeli.com/free "Bitdeli Badge") + +[gittip-url]: https://www.gittip.com/WeAreFractal/ +[gittip-image]: http://img.shields.io/gittip/WeAreFractal.svg + +[downloads-image]: http://img.shields.io/npm/dm/gulp.svg +[npm-url]: https://npmjs.org/package/gulp +[npm-image]: http://img.shields.io/npm/v/gulp.svg + +[travis-url]: https://travis-ci.org/gulpjs/gulp +[travis-image]: http://img.shields.io/travis/gulpjs/gulp.svg + +[coveralls-url]: https://coveralls.io/r/gulpjs/gulp +[coveralls-image]: http://img.shields.io/coveralls/gulpjs/gulp/master.svg diff --git a/completion/README.md b/completion/README.md new file mode 100644 index 00000000..c0e8c913 --- /dev/null +++ b/completion/README.md @@ -0,0 +1,20 @@ +# Completion for gulp +> Thanks to grunt team and Tyler Kellen + +To enable tasks auto-completion in shell you should add `eval "$(gulp --completion=shell)"` in your `.shellrc` file. + +## Bash + +Add `eval "$(gulp --completion=bash)"` to `~/.bashrc`. + +## Zsh + +Add `eval "$(gulp --completion=zsh)"` to `~/.zshrc`. + +## Powershell + +Add `Invoke-Expression ((gulp --completion=powershell) -join [System.Environment]::NewLine)` to `$PROFILE`. + +## Fish + +Add `gulp --completion=fish | source` to `~/.config/fish/config.fish`. diff --git a/completion/bash b/completion/bash new file mode 100644 index 00000000..704c27c1 --- /dev/null +++ b/completion/bash @@ -0,0 +1,27 @@ +#!/bin/bash + +# Borrowed from grunt-cli +# http://gruntjs.com/ +# +# Copyright (c) 2012 Tyler Kellen, contributors +# Licensed under the MIT license. +# https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT + +# Usage: +# +# To enable bash completion for gulp, add the following line (minus the +# leading #, which is the bash comment character) to your ~/.bashrc file: +# +# eval "$(gulp --completion=bash)" + +# Enable bash autocompletion. +function _gulp_completions() { + # The currently-being-completed word. + local cur="${COMP_WORDS[COMP_CWORD]}" + #Grab tasks + local compls=$(gulp --tasks-simple) + # Tell complete what stuff to show. + COMPREPLY=($(compgen -W "$compls" -- "$cur")) +} + +complete -o default -F _gulp_completions gulp diff --git a/completion/fish b/completion/fish new file mode 100644 index 00000000..f27f2248 --- /dev/null +++ b/completion/fish @@ -0,0 +1,10 @@ +#!/usr/bin/env fish + +# Usage: +# +# To enable fish completion for gulp, add the following line to +# your ~/.config/fish/config.fish file: +# +# gulp --completion=fish | source + +complete -c gulp -a "(gulp --tasks-simple)" -f diff --git a/completion/powershell b/completion/powershell new file mode 100644 index 00000000..08ec4382 --- /dev/null +++ b/completion/powershell @@ -0,0 +1,61 @@ +# Copyright (c) 2014 Jason Jarrett +# +# Tab completion for the `gulp` +# +# Usage: +# +# To enable powershell completion for gulp you need to be running +# at least PowerShell v3 or greater and add the below to your $PROFILE +# +# Invoke-Expression ((gulp --completion=powershell) -join [System.Environment]::NewLine) +# +# + +$gulp_completion_Process = { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) + + + # Load up an assembly to read the gulpfile's sha1 + if(-not $global:GulpSHA1Managed) { + [Reflection.Assembly]::LoadWithPartialName("System.Security") | out-null + $global:GulpSHA1Managed = new-Object System.Security.Cryptography.SHA1Managed + } + + # setup a global (in-memory) cache + if(-not $global:GulpfileShaCache) { + $global:GulpfileShaCache = @{}; + } + + $cache = $global:GulpfileShaCache; + + # Get the gulpfile's sha1 + $sha1gulpFile = (resolve-path gulpfile.js -ErrorAction Ignore | %{ + $file = [System.IO.File]::Open($_.Path, "open", "read") + [string]::join('', ($global:GulpSHA1Managed.ComputeHash($file) | %{ $_.ToString("x2") })) + $file.Dispose() + }) + + # lookup the sha1 for previously cached task lists. + if($cache.ContainsKey($sha1gulpFile)){ + $tasks = $cache[$sha1gulpFile]; + } else { + $tasks = (gulp --tasks-simple).split("`n"); + $cache[$sha1gulpFile] = $tasks; + } + + + $tasks | + where { $_.startswith($commandName) } + Sort-Object | + foreach { New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', ('{0}' -f $_) } +} + +if (-not $global:options) { + $global:options = @{ + CustomArgumentCompleters = @{}; + NativeArgumentCompleters = @{} + } +} + +$global:options['NativeArgumentCompleters']['gulp'] = $gulp_completion_Process +$function:tabexpansion2 = $function:tabexpansion2 -replace 'End\r\n{','End { if ($null -ne $options) { $options += $global:options} else {$options = $global:options}' diff --git a/completion/zsh b/completion/zsh new file mode 100644 index 00000000..8169b22d --- /dev/null +++ b/completion/zsh @@ -0,0 +1,25 @@ +#!/bin/zsh + +# Borrowed from grunt-cli +# http://gruntjs.com/ +# +# Copyright (c) 2012 Tyler Kellen, contributors +# Licensed under the MIT license. +# https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT + +# Usage: +# +# To enable zsh completion for gulp, add the following line (minus the +# leading #, which is the zsh comment character) to your ~/.zshrc file: +# +# eval "$(gulp --completion=zsh)" + +# Enable zsh autocompletion. +function _gulp_completion() { + # Grab tasks + compls=$(gulp --tasks-simple) + completions=(${=compls}) + compadd -- $completions +} + +compdef _gulp_completion gulp diff --git a/docs/CLI.md b/docs/CLI.md new file mode 100644 index 00000000..1490e9d2 --- /dev/null +++ b/docs/CLI.md @@ -0,0 +1,25 @@ +## gulp CLI docs + +### Flags + +gulp has very few flags to know about. All other flags are for tasks to use if needed. + +- `-v` or `--version` will display the global and local gulp versions +- `--require ` will require a module before running the gulpfile. This is useful for transpilers but also has other applications. You can use multiple `--require` flags +- `--gulpfile ` will manually set path of gulpfile. Useful if you have multiple gulpfiles. This will set the CWD to the gulpfile directory as well +- `--cwd ` will manually set the CWD. The search for the gulpfile, as well as the relativity of all requires will be from here +- `-T` or `--tasks` will display the task dependency tree for the loaded gulpfile +- `--tasks-simple` will display a plaintext list of tasks for the loaded gulpfile +- `--color` will force gulp and gulp plugins to display colors even when no color support is detected +- `--no-color` will force gulp and gulp plugins to not display colors even when color support is detected +- `--silent` will disable all gulp logging + +The CLI adds process.env.INIT_CWD which is the original cwd it was launched from. + +### Tasks + +Tasks can be executed by running `gulp `. Just running `gulp` will execute the task you registered called `default`. If there is no `default` task gulp will error. + +### Compilers + +You can find a list of supported languages at [interpret](https://github.com/tkellen/node-interpret#jsvariants). If you would like to add support for a new language send pull request/open issues there. diff --git a/index.js b/index.js new file mode 100755 index 00000000..589cd371 --- /dev/null +++ b/index.js @@ -0,0 +1,212 @@ +#!/usr/bin/env node + +'use strict'; +var gutil = require('gulp-util'); +var prettyTime = require('pretty-hrtime'); +var chalk = require('chalk'); +var semver = require('semver'); +var archy = require('archy'); +var Liftoff = require('liftoff'); +var tildify = require('tildify'); +var interpret = require('interpret'); +var v8flags = require('v8flags'); +var completion = require('./lib/completion'); +var argv = require('minimist')(process.argv.slice(2)); +var taskTree = require('./lib/taskTree'); + +// set env var for ORIGINAL cwd +// before anything touches it +process.env.INIT_CWD = process.cwd(); + +var cli = new Liftoff({ + name: 'gulp', + completions: completion, + extensions: interpret.jsVariants, + nodeFlags: v8flags.fetch() +}); + +// exit with 0 or 1 +var failed = false; +process.once('exit', function(code) { + if (code === 0 && failed) { + process.exit(1); + } +}); + +// parse those args m8 +var cliPackage = require('./package'); +var versionFlag = argv.v || argv.version; +var tasksFlag = argv.T || argv.tasks; +var tasks = argv._; +var toRun = tasks.length ? tasks : ['default']; + +// this is a hold-over until we have a better logging system +// with log levels +var simpleTasksFlag = argv['tasks-simple']; +var shouldLog = !argv.silent && !simpleTasksFlag; + +if (!shouldLog) { + gutil.log = function(){}; +} + +cli.on('require', function (name) { + gutil.log('Requiring external module', chalk.magenta(name)); +}); + +cli.on('requireFail', function (name) { + gutil.log(chalk.red('Failed to load external module'), chalk.magenta(name)); +}); + +cli.on('respawn', function (flags, child) { + var nodeFlags = chalk.magenta(flags.join(', ')); + var pid = chalk.magenta(child.pid); + gutil.log('Node flags detected:', nodeFlags); + gutil.log('Respawned to PID:', pid); +}); + +cli.launch({ + cwd: argv.cwd, + configPath: argv.gulpfile, + require: argv.require, + completion: argv.completion +}, handleArguments); + +// the actual logic +function handleArguments(env) { + if (versionFlag && tasks.length === 0) { + gutil.log('CLI version', cliPackage.version); + if (env.modulePackage && typeof env.modulePackage.version !== 'undefined') { + gutil.log('Local version', env.modulePackage.version); + } + process.exit(0); + } + + if (!env.modulePath) { + gutil.log( + chalk.red('Local gulp not found in'), + chalk.magenta(tildify(env.cwd)) + ); + gutil.log(chalk.red('Try running: npm install gulp')); + process.exit(1); + } + + if (!env.configPath) { + gutil.log(chalk.red('No gulpfile found')); + process.exit(1); + } + + // check for semver difference between cli and local installation + if (semver.gt(cliPackage.version, env.modulePackage.version)) { + gutil.log(chalk.red('Warning: gulp version mismatch:')); + gutil.log(chalk.red('Global gulp is', cliPackage.version)); + gutil.log(chalk.red('Local gulp is', env.modulePackage.version)); + } + + // chdir before requiring gulpfile to make sure + // we let them chdir as needed + if (process.cwd() !== env.cwd) { + process.chdir(env.cwd); + gutil.log( + 'Working directory changed to', + chalk.magenta(tildify(env.cwd)) + ); + } + + // this is what actually loads up the gulpfile + require(env.configPath); + gutil.log('Using gulpfile', chalk.magenta(tildify(env.configPath))); + + var gulpInst = require(env.modulePath); + logEvents(gulpInst); + + process.nextTick(function () { + if (simpleTasksFlag) { + return logTasksSimple(env, gulpInst); + } + if (tasksFlag) { + return logTasks(env, gulpInst); + } + gulpInst.start.apply(gulpInst, toRun); + }); +} + +function logTasks(env, localGulp) { + var tree = taskTree(localGulp.tasks); + tree.label = 'Tasks for ' + chalk.magenta(tildify(env.configPath)); + archy(tree) + .split('\n') + .forEach(function (v) { + if (v.trim().length === 0) { + return; + } + gutil.log(v); + }); +} + +function logTasksSimple(env, localGulp) { + console.log(Object.keys(localGulp.tasks) + .join('\n') + .trim()); +} + +// format orchestrator errors +function formatError(e) { + if (!e.err) { + return e.message; + } + + // PluginError + if (typeof e.err.showStack === 'boolean') { + return e.err.toString(); + } + + // normal error + if (e.err.stack) { + return e.err.stack; + } + + // unknown (string, number, etc.) + return new Error(String(e.err)).stack; +} + +// wire up logging events +function logEvents(gulpInst) { + + // total hack due to poor error management in orchestrator + gulpInst.on('err', function () { + failed = true; + }); + + gulpInst.on('task_start', function (e) { + // TODO: batch these + // so when 5 tasks start at once it only logs one time with all 5 + gutil.log('Starting', '\'' + chalk.cyan(e.task) + '\'...'); + }); + + gulpInst.on('task_stop', function (e) { + var time = prettyTime(e.hrDuration); + gutil.log( + 'Finished', '\'' + chalk.cyan(e.task) + '\'', + 'after', chalk.magenta(time) + ); + }); + + gulpInst.on('task_err', function (e) { + var msg = formatError(e); + var time = prettyTime(e.hrDuration); + gutil.log( + '\'' + chalk.cyan(e.task) + '\'', + chalk.red('errored after'), + chalk.magenta(time) + ); + gutil.log(msg); + }); + + gulpInst.on('task_not_found', function (err) { + gutil.log( + chalk.red('Task \'' + err.task + '\' is not in your gulpfile') + ); + gutil.log('Please check the documentation for proper gulpfile formatting'); + process.exit(1); + }); +} diff --git a/lib/completion.js b/lib/completion.js new file mode 100644 index 00000000..5f2ad364 --- /dev/null +++ b/lib/completion.js @@ -0,0 +1,22 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); + +module.exports = function (name) { + if (typeof name !== 'string') { + throw new Error('Missing completion type'); + } + var file = path.join(__dirname, '../completion', name); + try { + console.log(fs.readFileSync(file, 'utf8')); + process.exit(0); + } catch (err) { + console.log( + 'echo "gulp autocompletion rules for', + '\'' + name + '\'', + 'not found"' + ); + process.exit(5); + } +}; diff --git a/lib/taskTree.js b/lib/taskTree.js new file mode 100644 index 00000000..8a638f7a --- /dev/null +++ b/lib/taskTree.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = function (tasks) { + return Object.keys(tasks) + .reduce(function (prev, task) { + prev.nodes.push({ + label: task, + nodes: tasks[task].dep + }); + return prev; + }, { + nodes: [] + }); +}; diff --git a/package.json b/package.json new file mode 100644 index 00000000..dd2a2a98 --- /dev/null +++ b/package.json @@ -0,0 +1,69 @@ +{ + "name": "gulp-cli", + "description": "The streaming build system", + "version": "0.0.0", + "homepage": "http://gulpjs.com", + "repository": "gulpjs/gulp-cli", + "author": "Fractal (http://wearefractal.com/)", + "tags": [ + "build", + "stream", + "system", + "make", + "tool", + "asset", + "pipeline" + ], + "files": [ + "index.js", + "lib", + "completion" + ], + "bin": { + "gulp": "./index.js" + }, + "man": "gulp.1", + "dependencies": { + "archy": "^1.0.0", + "chalk": "^0.5.0", + "gulp-util": "^3.0.0", + "interpret": "^0.3.2", + "liftoff": "^1.0.0", + "minimist": "^1.1.0", + "pretty-hrtime": "^0.2.0", + "semver": "^4.1.0", + "tildify": "^1.0.0", + "v8flags": "^1.0.1" + }, + "devDependencies": { + "coveralls": "^2.7.0", + "graceful-fs": "^3.0.0", + "gulp": "^3.8.10", + "istanbul": "^0.3.0", + "jshint": "^2.5.0", + "jshint-stylish": "^1.0.0", + "marked-man": "^0.1.3", + "mkdirp": "^0.5.0", + "mocha": "^2.0.1", + "mocha-lcov-reporter": "^0.0.1", + "q": "^1.0.0", + "rimraf": "^2.2.5", + "should": "^4.0.0" + }, + "scripts": { + "prepublish": "marked-man --name gulp docs/CLI.md > gulp.1", + "lint": "jshint lib bin index.js --reporter node_modules/jshint-stylish/stylish.js --exclude node_modules", + "test": "npm run-script lint && mocha --reporter spec", + "coveralls": "istanbul cover _mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage" + }, + "engineStrict": true, + "engines": { + "node": ">= 0.9" + }, + "licenses": [ + { + "type": "MIT", + "url": "https://raw.githubusercontent.com/gulpjs/gulp/master/LICENSE" + } + ] +} diff --git a/test/gulpfile.js b/test/gulpfile.js new file mode 100644 index 00000000..bea0366d --- /dev/null +++ b/test/gulpfile.js @@ -0,0 +1,7 @@ +'use strict'; + +var gulp = require('gulp'); + +gulp.task('default', function(){ + +});