Comparing version 0.12.0 to 1.0.0-beta.1
296
lib/git.js
var cp = require('child_process'); | ||
var fs = require('fs-extra'); | ||
var path = require('path'); | ||
var util = require('util'); | ||
var Q = require('q'); | ||
var fs = require('q-io/fs'); | ||
var git = 'git'; | ||
/** | ||
@@ -26,3 +21,2 @@ * @constructor | ||
/** | ||
@@ -36,93 +30,66 @@ * Util function for handling spawned processes as promises. | ||
function spawn(exe, args, cwd) { | ||
var deferred = Q.defer(); | ||
var child = cp.spawn(exe, args, {cwd: cwd || process.cwd()}); | ||
var buffer = []; | ||
child.stderr.on('data', function(chunk) { | ||
buffer.push(chunk.toString()); | ||
return new Promise(function(resolve, reject) { | ||
var child = cp.spawn(exe, args, {cwd: cwd || process.cwd()}); | ||
var buffer = []; | ||
child.stderr.on('data', function(chunk) { | ||
buffer.push(chunk.toString()); | ||
}); | ||
child.stdout.on('data', function(chunk) { | ||
buffer.push(chunk.toString()); | ||
}); | ||
child.on('close', function(code) { | ||
var output = buffer.join(''); | ||
if (code) { | ||
var msg = output || 'Process failed: ' + code; | ||
reject(new ProcessError(code, msg)); | ||
} else { | ||
resolve(output); | ||
} | ||
}); | ||
}); | ||
child.stdout.on('data', function(chunk) { | ||
deferred.notify(chunk); | ||
}); | ||
child.on('close', function(code) { | ||
if (code) { | ||
var msg = buffer.join('') || 'Process failed: ' + code; | ||
deferred.reject(new ProcessError(code, msg)); | ||
} else { | ||
deferred.resolve(code); | ||
} | ||
}); | ||
return deferred.promise; | ||
} | ||
/** | ||
* Execute a git command. | ||
* @param {Array.<string>} args Arguments (e.g. ['remote', 'update']). | ||
* Create an object for executing git commands. | ||
* @param {string} cwd Repository directory. | ||
* @return {Promise} A promise. The promise will be resolved with the exit code | ||
* or rejected with an error. To get stdout, use a progress listener (e.g. | ||
* `promise.progress(function(chunk) {console.log(String(chunk);}))`). | ||
* @param {string} exe Git executable (full path if not already on path). | ||
* @constructor | ||
*/ | ||
exports = module.exports = function(args, cwd) { | ||
return spawn(git, args, cwd); | ||
}; | ||
function Git(cwd, cmd) { | ||
this.cwd = cwd; | ||
this.cmd = cmd || 'git'; | ||
this.output = ''; | ||
} | ||
/** | ||
* Set the Git executable to be used by exported methods (defaults to 'git'). | ||
* @param {string} exe Git executable (full path if not already on path). | ||
* Execute an arbitrary git command. | ||
* @param {string} var_args Arguments (e.g. 'remote', 'update'). | ||
* @return {Promise} A promise. The promise will be resolved with this instance | ||
* or rejected with an error. | ||
*/ | ||
exports.exe = function(exe) { | ||
git = exe; | ||
Git.prototype.exec = function() { | ||
return spawn(this.cmd, [].slice.call(arguments), this.cwd).then( | ||
function(output) { | ||
this.output = output; | ||
return this; | ||
}.bind(this) | ||
); | ||
}; | ||
/** | ||
* Initialize repository. | ||
* @param {string} cwd Repository directory. | ||
* @return {ChildProcess} Child process. | ||
*/ | ||
exports.init = function init(cwd) { | ||
return spawn(git, ['init'], cwd); | ||
Git.prototype.init = function() { | ||
return this.exec('init'); | ||
}; | ||
/** | ||
* Clone a repo into the given dir if it doesn't already exist. | ||
* @param {string} repo Repository URL. | ||
* @param {string} dir Target directory. | ||
* @param {string} branch Branch name. | ||
* @param {options} options All options. | ||
* @return {Promise} A promise. | ||
*/ | ||
exports.clone = function clone(repo, dir, branch, options) { | ||
return fs.exists(dir).then(function(exists) { | ||
if (exists) { | ||
return Q.resolve(); | ||
} else { | ||
return fs.makeTree(path.dirname(path.resolve(dir))).then(function() { | ||
var args = ['clone', repo, dir, '--branch', branch, '--single-branch', '--origin', options.remote]; | ||
if (options.depth) { | ||
args.push('--depth', options.depth); | ||
} | ||
return spawn(git, args).fail(function(err) { | ||
// try again without banch options | ||
return spawn(git, ['clone', repo, dir, '--origin', options.remote]); | ||
}); | ||
}); | ||
} | ||
}); | ||
}; | ||
/** | ||
* Clean up unversioned files. | ||
* @param {string} cwd Repository directory. | ||
* @return {Promise} A promise. | ||
*/ | ||
var clean = exports.clean = function clean(cwd) { | ||
return spawn(git, ['clean', '-f', '-d'], cwd); | ||
Git.prototype.clean = function() { | ||
return this.exec('clean', '-f', '-d'); | ||
}; | ||
/** | ||
@@ -132,21 +99,17 @@ * Hard reset to remote/branch | ||
* @param {string} branch Branch name. | ||
* @param {string} cwd Repository directory. | ||
* @return {Promise} A promise. | ||
*/ | ||
var reset = exports.reset = function reset(remote, branch, cwd) { | ||
return spawn(git, ['reset', '--hard', remote + '/' + branch], cwd); | ||
Git.prototype.reset = function(remote, branch) { | ||
return this.exec('reset', '--hard', remote + '/' + branch); | ||
}; | ||
/** | ||
* Fetch from a remote. | ||
* @param {string} remote Remote alias. | ||
* @param {string} cwd Repository directory. | ||
* @return {Promise} A promise. | ||
*/ | ||
exports.fetch = function fetch(remote, cwd) { | ||
return spawn(git, ['fetch', remote], cwd); | ||
Git.prototype.fetch = function(remote) { | ||
return this.exec('fetch', remote); | ||
}; | ||
/** | ||
@@ -156,80 +119,73 @@ * Checkout a branch (create an orphan if it doesn't exist on the remote). | ||
* @param {string} branch Branch name. | ||
* @param {string} cwd Repository directory. | ||
* @return {Promise} A promise. | ||
*/ | ||
exports.checkout = function checkout(remote, branch, cwd) { | ||
Git.prototype.checkout = function(remote, branch) { | ||
var treeish = remote + '/' + branch; | ||
return spawn(git, ['ls-remote', '--exit-code', '.', treeish], cwd) | ||
.then(function() { | ||
// branch exists on remote, hard reset | ||
return spawn(git, ['checkout', branch], cwd) | ||
.then(function() { | ||
return clean(cwd); | ||
}) | ||
.then(function() { | ||
return reset(remote, branch, cwd); | ||
}); | ||
}, function(error) { | ||
if (error instanceof ProcessError && error.code === 2) { | ||
// branch doesn't exist, create an orphan | ||
return spawn(git, ['checkout', '--orphan', branch], cwd); | ||
} else { | ||
// unhandled error | ||
return Q.reject(error); | ||
} | ||
}); | ||
return this.exec('ls-remote', '--exit-code', '.', treeish).then( | ||
function() { | ||
// branch exists on remote, hard reset | ||
return this.exec('checkout', branch) | ||
.then( | ||
function() { | ||
return this.clean(); | ||
}.bind(this) | ||
) | ||
.then( | ||
function() { | ||
return this.reset(remote, branch); | ||
}.bind(this) | ||
); | ||
}.bind(this), | ||
function(error) { | ||
if (error instanceof ProcessError && error.code === 2) { | ||
// branch doesn't exist, create an orphan | ||
return this.exec('checkout', '--orphan', branch); | ||
} else { | ||
// unhandled error | ||
throw error; | ||
} | ||
}.bind(this) | ||
); | ||
}; | ||
/** | ||
* Remove all unversioned files. | ||
* @param {string} files Files argument. | ||
* @param {string} cwd Repository directory. | ||
* @return {Promise} A promise. | ||
*/ | ||
exports.rm = function rm(files, cwd) { | ||
return spawn(git, ['rm', '--ignore-unmatch', '-r', '-f', files], cwd); | ||
Git.prototype.rm = function(files) { | ||
return this.exec('rm', '--ignore-unmatch', '-r', '-f', files); | ||
}; | ||
/** | ||
* Add files. | ||
* @param {string} files Files argument. | ||
* @param {string} cwd Repository directory. | ||
* @return {Promise} A promise. | ||
*/ | ||
exports.add = function add(files, cwd) { | ||
return spawn(git, ['add', files], cwd); | ||
Git.prototype.add = function(files) { | ||
return this.exec('add', files); | ||
}; | ||
/** | ||
* Commit. | ||
* Commit (if there are any changes). | ||
* @param {string} message Commit message. | ||
* @param {string} cwd Repository directory. | ||
* @return {Promise} A promise. | ||
*/ | ||
exports.commit = function commit(message, cwd) { | ||
return spawn(git, ['diff-index', '--quiet', 'HEAD', '.'], cwd) | ||
.then(function() { | ||
// nothing to commit | ||
return Q.resolve(); | ||
}) | ||
.fail(function() { | ||
return spawn(git, ['commit', '-m', message], cwd); | ||
}); | ||
Git.prototype.commit = function(message) { | ||
return this.exec('diff-index', '--quiet', 'HEAD').catch( | ||
function() { | ||
return this.exec('commit', '-m', message); | ||
}.bind(this) | ||
); | ||
}; | ||
/** | ||
* Add tag | ||
* @param {string} name Name of tag. | ||
* @param {string} cwd Repository directory. | ||
* @return {Promise} A promise. | ||
*/ | ||
exports.tag = function tag(name, cwd) { | ||
return spawn(git, ['tag', name], cwd); | ||
Git.prototype.tag = function(name) { | ||
return this.exec('tag', name); | ||
}; | ||
/** | ||
@@ -239,7 +195,83 @@ * Push a branch. | ||
* @param {string} branch Branch name. | ||
* @param {string} cwd Repository directory. | ||
* @return {Promise} A promise. | ||
*/ | ||
exports.push = function push(remote, branch, cwd) { | ||
return spawn(git, ['push', '--tags', remote, branch], cwd); | ||
Git.prototype.push = function(remote, branch) { | ||
return this.exec('push', '--tags', remote, branch); | ||
}; | ||
/** | ||
* Get the URL for a remote. | ||
* @param {string} remote Remote alias. | ||
* @return {Promise<string>} A promise for the remote URL. | ||
*/ | ||
Git.prototype.getRemoteUrl = function(remote) { | ||
return this.exec('config', '--get', 'remote.' + remote + '.url') | ||
.then(function(git) { | ||
var repo = git.output && git.output.split(/[\n\r]/).shift(); | ||
if (repo) { | ||
return repo; | ||
} else { | ||
throw new Error( | ||
'Failed to get repo URL from options or current directory.' | ||
); | ||
} | ||
}) | ||
.catch(function(err) { | ||
throw new Error( | ||
'Failed to get remote.' + | ||
remote + | ||
'.url (task must either be ' + | ||
'run in a git repository with a configured ' + | ||
remote + | ||
' remote ' + | ||
'or must be configured with the "repo" option).' | ||
); | ||
}); | ||
}; | ||
/** | ||
* Clone a repo into the given dir if it doesn't already exist. | ||
* @param {string} repo Repository URL. | ||
* @param {string} dir Target directory. | ||
* @param {string} branch Branch name. | ||
* @param {options} options All options. | ||
* @return {Promise<Git>} A promise. | ||
*/ | ||
Git.clone = function clone(repo, dir, branch, options) { | ||
return fs.exists(dir).then(function(exists) { | ||
if (exists) { | ||
return Promise.resolve(new Git(dir, options.git)); | ||
} else { | ||
return fs.mkdirp(path.dirname(path.resolve(dir))).then(function() { | ||
var args = [ | ||
'clone', | ||
repo, | ||
dir, | ||
'--branch', | ||
branch, | ||
'--single-branch', | ||
'--origin', | ||
options.remote, | ||
'--depth', | ||
options.depth | ||
]; | ||
return spawn(options.git, args) | ||
.catch(function(err) { | ||
// try again without banch or depth options | ||
return spawn(options.git, [ | ||
'clone', | ||
repo, | ||
dir, | ||
'--origin', | ||
options.remote | ||
]); | ||
}) | ||
.then(function() { | ||
return new Git(dir, options.git); | ||
}); | ||
}); | ||
} | ||
}); | ||
}; | ||
module.exports = Git; |
295
lib/index.js
@@ -0,12 +1,11 @@ | ||
var Git = require('./git'); | ||
var base64url = require('base64url'); | ||
var copy = require('./util').copy; | ||
var fs = require('fs-extra'); | ||
var globby = require('globby'); | ||
var path = require('path'); | ||
var fs = require('fs'); | ||
var util = require('util'); | ||
var Q = require('q'); | ||
var rimraf = require('rimraf'); | ||
var globby = require('globby'); | ||
var log = util.debuglog('gh-pages'); | ||
var git = require('./git'); | ||
var copy = require('./util').copy; | ||
function getCacheDir() { | ||
@@ -16,33 +15,11 @@ return path.relative(process.cwd(), path.resolve(__dirname, '../.cache')); | ||
function getRemoteUrl(dir, remote) { | ||
var repo; | ||
return git(['config', '--get', 'remote.' + remote + '.url'], dir) | ||
.progress(function(chunk) { | ||
repo = String(chunk).split(/[\n\r]/).shift(); | ||
}) | ||
.then(function() { | ||
if (repo) { | ||
return Q.resolve(repo); | ||
} else { | ||
return Q.reject(new Error( | ||
'Failed to get repo URL from options or current directory.')); | ||
} | ||
}) | ||
.fail(function(err) { | ||
return Q.reject(new Error( | ||
'Failed to get remote.' + remote + '.url (task must either be ' + | ||
'run in a git repository with a configured ' + remote + ' remote ' + | ||
'or must be configured with the "repo" option).')); | ||
}); | ||
} | ||
function getRepo(options) { | ||
if (options.repo) { | ||
return Q.resolve(options.repo); | ||
return Promise.resolve(options.repo); | ||
} else { | ||
return getRemoteUrl(process.cwd(), options.remote); | ||
var git = new Git(process.cwd(), options.git); | ||
return git.getRemoteUrl(options.remote); | ||
} | ||
} | ||
/** | ||
@@ -61,5 +38,6 @@ * Push a git branch to a remote (pushes gh-pages by default). | ||
var defaults = { | ||
dest: '.', | ||
add: false, | ||
git: 'git', | ||
clone: getCacheDir(), | ||
depth: 1, | ||
dotfiles: false, | ||
@@ -72,22 +50,7 @@ branch: 'gh-pages', | ||
message: 'Updates', | ||
silent: false, | ||
logger: function() {} | ||
silent: false | ||
}; | ||
// override defaults with any task options | ||
// TODO: Require Node >= 4 and use Object.assign | ||
var options = {}; | ||
for (var d in defaults) { | ||
options[d] = defaults[d]; | ||
} | ||
for (var c in config) { | ||
options[c] = config[c]; | ||
} | ||
var options = Object.assign({}, defaults, config); | ||
function log(message) { | ||
if (!options.silent) { | ||
options.logger(message); | ||
} | ||
} | ||
if (!callback) { | ||
@@ -105,3 +68,3 @@ callback = function(err) { | ||
} catch (err2) { | ||
log('Publish callback threw: ', err2.message); | ||
log('Publish callback threw: %s', err2.message); | ||
} | ||
@@ -120,126 +83,136 @@ } | ||
var files = globby.sync(options.src, { | ||
cwd: basePath, | ||
dot: options.dotfiles | ||
}).filter(function(file) { | ||
return !fs.statSync(path.join(basePath, file)).isDirectory(); | ||
}); | ||
var files = globby | ||
.sync(options.src, { | ||
cwd: basePath, | ||
dot: options.dotfiles | ||
}) | ||
.filter(function(file) { | ||
return !fs.statSync(path.join(basePath, file)).isDirectory(); | ||
}); | ||
if (!Array.isArray(files) || files.length === 0) { | ||
done(new Error('Files must be provided in the "src" property.')); | ||
done( | ||
new Error('The pattern in the "src" property didn\'t match any files.') | ||
); | ||
return; | ||
} | ||
var only = globby.sync(options.only, {cwd: basePath}); | ||
var only = globby.sync(options.only, {cwd: basePath}).map(function(file) { | ||
return path.join(options.dest, file); | ||
}); | ||
git.exe(options.git); | ||
var repoUrl; | ||
getRepo(options) | ||
.then(function(repo) { | ||
repoUrl = repo; | ||
log('Cloning ' + repo + ' into ' + options.clone); | ||
return git.clone(repo, options.clone, options.branch, options); | ||
}) | ||
.then(function() { | ||
return getRemoteUrl(options.clone, options.remote) | ||
.then(function(url) { | ||
if (url !== repoUrl) { | ||
var message = 'Remote url mismatch. Got "' + url + '" ' + | ||
'but expected "' + repoUrl + '" in ' + options.clone + | ||
'. If you have changed your "repo" option, try ' + | ||
'running the "clean" task first.'; | ||
return Q.reject(new Error(message)); | ||
} else { | ||
return Q.resolve(); | ||
} | ||
}); | ||
}) | ||
.then(function() { | ||
// only required if someone mucks with the checkout between builds | ||
log('Cleaning'); | ||
return git.clean(options.clone); | ||
}) | ||
.then(function() { | ||
log('Fetching ' + options.remote); | ||
return git.fetch(options.remote, options.clone); | ||
}) | ||
.then(function() { | ||
log('Checking out ' + options.remote + '/' + | ||
options.branch); | ||
return git.checkout(options.remote, options.branch, | ||
options.clone); | ||
}) | ||
.then(function() { | ||
if (!options.add) { | ||
log('Removing files'); | ||
return git.rm(only.join(' '), options.clone); | ||
} else { | ||
return Q.resolve(); | ||
.then(function(repo) { | ||
repoUrl = repo; | ||
var clone = options.clone; | ||
if (!clone) { | ||
clone = path.join(getCacheDir(), base64url(repo)); | ||
} | ||
log('Cloning %s into %s', repo, clone); | ||
return Git.clone(repo, clone, options.branch, options); | ||
}) | ||
.then(function(git) { | ||
return git.getRemoteUrl(options.remote).then(function(url) { | ||
if (url !== repoUrl) { | ||
var message = | ||
'Remote url mismatch. Got "' + | ||
url + | ||
'" ' + | ||
'but expected "' + | ||
repoUrl + | ||
'" in ' + | ||
git.cwd + | ||
'. Try running the `gh-pages-clean` script first.'; | ||
throw new Error(message); | ||
} | ||
}) | ||
.then(function() { | ||
log('Copying files'); | ||
return copy(files, basePath, options.clone); | ||
}) | ||
.then(function() { | ||
log('Adding all'); | ||
return git.add('.', options.clone); | ||
}) | ||
.then(function() { | ||
if (options.user) { | ||
return git(['config', 'user.email', options.user.email], | ||
options.clone) | ||
.then(function() { | ||
return git(['config', 'user.name', options.user.name], | ||
options.clone); | ||
}); | ||
} else { | ||
return Q.resolve(); | ||
} | ||
}) | ||
.then(function() { | ||
log('Committing'); | ||
return git.commit(options.message, options.clone); | ||
}) | ||
.then(function() { | ||
if (options.tag) { | ||
log('Tagging'); | ||
var deferred = Q.defer(); | ||
git.tag(options.tag, options.clone) | ||
.then(function() { | ||
return deferred.resolve(); | ||
}) | ||
.fail(function(error) { | ||
// tagging failed probably because this tag alredy exists | ||
log('Tagging failed, continuing'); | ||
options.logger(error); | ||
return deferred.resolve(); | ||
}); | ||
return deferred.promise; | ||
} else { | ||
return Q.resolve(); | ||
} | ||
}) | ||
.then(function() { | ||
if (options.push) { | ||
log('Pushing'); | ||
return git.push(options.remote, options.branch, | ||
options.clone); | ||
} else { | ||
return Q.resolve(); | ||
} | ||
}) | ||
.then(function() { | ||
return git; | ||
}); | ||
}) | ||
.then(function(git) { | ||
// only required if someone mucks with the checkout between builds | ||
log('Cleaning'); | ||
return git.clean(); | ||
}) | ||
.then(function(git) { | ||
log('Fetching %s', options.remote); | ||
return git.fetch(options.remote); | ||
}) | ||
.then(function(git) { | ||
log('Checking out %s/%s ', options.remote, options.branch); | ||
return git.checkout(options.remote, options.branch); | ||
}) | ||
.then(function(git) { | ||
if (!options.add) { | ||
log('Removing files'); | ||
return git.rm(only.join(' ')); | ||
} else { | ||
return git; | ||
} | ||
}) | ||
.then(function(git) { | ||
log('Copying files'); | ||
return copy( | ||
files, | ||
basePath, | ||
path.join(git.cwd, options.dest) | ||
).then(function() { | ||
return git; | ||
}); | ||
}) | ||
.then(function(git) { | ||
log('Adding all'); | ||
return git.add('.'); | ||
}) | ||
.then(function(git) { | ||
if (options.user) { | ||
return git | ||
.exec('config', 'user.email', options.user.email) | ||
.then(function() { | ||
return git.exec('config', 'user.name', options.user.name); | ||
}); | ||
} else { | ||
return git; | ||
} | ||
}) | ||
.then(function(git) { | ||
log('Committing'); | ||
return git.commit(options.message); | ||
}) | ||
.then(function(git) { | ||
if (options.tag) { | ||
log('Tagging'); | ||
return git.tag(options.tag).catch(function(error) { | ||
// tagging failed probably because this tag alredy exists | ||
log(error); | ||
log('Tagging failed, continuing'); | ||
return git; | ||
}); | ||
} else { | ||
return git; | ||
} | ||
}) | ||
.then(function(git) { | ||
if (options.push) { | ||
log('Pushing'); | ||
return git.push(options.remote, options.branch); | ||
} else { | ||
return git; | ||
} | ||
}) | ||
.then( | ||
function() { | ||
done(); | ||
}, function(error) { | ||
}, | ||
function(error) { | ||
if (options.silent) { | ||
error = new Error( | ||
'Unspecified error (run without silent option for detail)'); | ||
'Unspecified error (run without silent option for detail)' | ||
); | ||
} | ||
done(error); | ||
}); | ||
} | ||
); | ||
}; | ||
/** | ||
@@ -249,3 +222,3 @@ * Clean the cache directory. | ||
exports.clean = function clean() { | ||
rimraf.sync(getCacheDir()); | ||
fs.removeSync(getCacheDir()); | ||
}; |
@@ -5,5 +5,3 @@ var path = require('path'); | ||
var fs = require('graceful-fs'); | ||
var Q = require('q'); | ||
/** | ||
@@ -14,7 +12,7 @@ * Generate a list of unique directory paths given a list of file paths. | ||
*/ | ||
var uniqueDirs = exports.uniqueDirs = function(files) { | ||
var uniqueDirs = (exports.uniqueDirs = function(files) { | ||
var dirs = {}; | ||
files.forEach(function(filepath) { | ||
var parts = path.dirname(filepath).split(path.sep); | ||
var partial = parts[0]; | ||
var partial = parts[0] || '/'; | ||
dirs[partial] = true; | ||
@@ -27,5 +25,4 @@ for (var i = 1, ii = parts.length; i < ii; ++i) { | ||
return Object.keys(dirs); | ||
}; | ||
}); | ||
/** | ||
@@ -38,3 +35,3 @@ * Sort function for paths. Sorter paths come first. Paths of equal length are | ||
*/ | ||
var byShortPath = exports.byShortPath = function(a, b) { | ||
var byShortPath = (exports.byShortPath = function(a, b) { | ||
var aParts = a.split(path.sep); | ||
@@ -64,5 +61,4 @@ var bParts = b.split(path.sep); | ||
return cmp; | ||
}; | ||
}); | ||
/** | ||
@@ -73,7 +69,6 @@ * Generate a list of directories to create given a list of file paths. | ||
*/ | ||
var dirsToCreate = exports.dirsToCreate = function(files) { | ||
var dirsToCreate = (exports.dirsToCreate = function(files) { | ||
return uniqueDirs(files).sort(byShortPath); | ||
}; | ||
}); | ||
/** | ||
@@ -84,3 +79,3 @@ * Copy a file. | ||
*/ | ||
var copyFile = exports.copyFile = function(obj, callback) { | ||
var copyFile = (exports.copyFile = function(obj, callback) { | ||
var called = false; | ||
@@ -108,5 +103,4 @@ function done(err) { | ||
read.pipe(write); | ||
}; | ||
}); | ||
/** | ||
@@ -134,3 +128,2 @@ * Make directory, ignoring errors if directory already exists. | ||
/** | ||
@@ -144,31 +137,29 @@ * Copy a list of files. | ||
exports.copy = function(files, base, dest) { | ||
var deferred = Q.defer(); | ||
var pairs = []; | ||
var destFiles = []; | ||
files.forEach(function(file) { | ||
var src = path.resolve(base, file); | ||
var relative = path.relative(base, src); | ||
var target = path.join(dest, relative); | ||
pairs.push({ | ||
src: src, | ||
dest: target | ||
return new Promise(function(resolve, reject) { | ||
var pairs = []; | ||
var destFiles = []; | ||
files.forEach(function(file) { | ||
var src = path.resolve(base, file); | ||
var relative = path.relative(base, src); | ||
var target = path.join(dest, relative); | ||
pairs.push({ | ||
src: src, | ||
dest: target | ||
}); | ||
destFiles.push(target); | ||
}); | ||
destFiles.push(target); | ||
}); | ||
async.eachSeries(dirsToCreate(destFiles), makeDir, function(err) { | ||
if (err) { | ||
return deferred.reject(err); | ||
} | ||
async.each(pairs, copyFile, function(err) { | ||
async.eachSeries(dirsToCreate(destFiles), makeDir, function(err) { | ||
if (err) { | ||
return deferred.reject(err); | ||
} else { | ||
return deferred.resolve(); | ||
return reject(err); | ||
} | ||
async.each(pairs, copyFile, function(err) { | ||
if (err) { | ||
return reject(err); | ||
} else { | ||
return resolve(); | ||
} | ||
}); | ||
}); | ||
}); | ||
return deferred.promise; | ||
}; |
{ | ||
"name": "gh-pages", | ||
"version": "0.12.0", | ||
"version": "1.0.0-beta.1", | ||
"description": "Publish to a gh-pages branch on GitHub (or any other branch on any other remote)", | ||
@@ -24,13 +24,20 @@ "keywords": [ | ||
"main": "lib/index.js", | ||
"config": { | ||
"js": "lib test bin plugin.js" | ||
}, | ||
"scripts": { | ||
"pretest": "eslint lib test bin/gh-pages", | ||
"fix-lint": "eslint --fix $npm_package_config_js", | ||
"pretest": "eslint $npm_package_config_js", | ||
"test": "mocha --recursive test" | ||
}, | ||
"engines": { | ||
"node": ">=4" | ||
}, | ||
"dependencies": { | ||
"async": "2.1.2", | ||
"async": "2.1.4", | ||
"base64url": "^2.0.0", | ||
"commander": "2.9.0", | ||
"fs-extra": "^3.0.1", | ||
"globby": "^6.1.0", | ||
"graceful-fs": "4.1.10", | ||
"q": "1.4.1", | ||
"q-io": "1.13.2", | ||
"graceful-fs": "4.1.11", | ||
"rimraf": "^2.5.4" | ||
@@ -40,8 +47,12 @@ }, | ||
"chai": "^3.5.0", | ||
"dir-compare": "^1.4.0", | ||
"eslint": "^3.10.2", | ||
"eslint-config-tschaub": "^6.0.0", | ||
"mocha": "^3.1.2" | ||
"eslint-config-tschaub": "^7.0.0", | ||
"mocha": "^3.1.2", | ||
"sinon": "^1.17.3", | ||
"tmp": "0.0.31" | ||
}, | ||
"bin": { | ||
"gh-pages": "bin/gh-pages" | ||
"gh-pages": "bin/gh-pages", | ||
"gh-pages-clean": "bin/gh-pages-clean" | ||
}, | ||
@@ -48,0 +59,0 @@ "eslintConfig": { |
# gh-pages | ||
[![Greenkeeper badge](https://badges.greenkeeper.io/tschaub/gh-pages.svg)](https://greenkeeper.io/) | ||
Publish files to a `gh-pages` branch on GitHub (or any other branch anywhere else). | ||
@@ -11,3 +13,3 @@ | ||
This module requires Git `>=1.7.6`. | ||
This module requires Git `>=1.9`. | ||
@@ -68,3 +70,3 @@ ## Basic Usage | ||
The default options work for simple cases cases. The options described below let you push to alternate branches, customize your commit messages, and more. | ||
The default options work for simple cases. The options described below let you push to alternate branches, customize your commit messages, and more. | ||
@@ -267,36 +269,34 @@ | ||
#### <a id="optionslogger">options.logger</a> | ||
* type: `function(string)` | ||
* default: `function(){}` | ||
#### <a id="optionsgit">options.git</a> | ||
* type: `string` | ||
* default: `'git'` | ||
Logger function. The default logging function is a no-op, allowing you to provide a custom logging implementation. | ||
Your `git` executable. | ||
Example use of the `logger` option: | ||
Example use of the `git` option: | ||
```js | ||
/** | ||
* This configuration will log to the console | ||
* If `git` is not on your path, provide the path as shown below. | ||
*/ | ||
ghpages.publish(path.join(__dirname, 'build'), { | ||
logger: function(message) { | ||
console.log(message); | ||
} | ||
git: '/path/to/git' | ||
}, callback); | ||
``` | ||
#### <a id="optionsgit">options.git</a> | ||
#### <a id="optionsdest">options.dest</a> | ||
* type: `string` | ||
* default: `'git'` | ||
* default: `'.'` | ||
Your `git` executable. | ||
The destination folder within the destination branch/repository. | ||
Example use of the `git` option: | ||
Example use of the `dest` option: | ||
```js | ||
/** | ||
* If `git` is not on your path, provide the path as shown below. | ||
* Place content in the static/project subdirectory of the target | ||
* branch/repository. If removing files, only remove static/project. | ||
*/ | ||
ghpages.publish(path.join(__dirname, 'build'), { | ||
git: '/path/to/git' | ||
dest: 'static/project' | ||
}, callback); | ||
@@ -323,6 +323,14 @@ ``` | ||
## Debugging | ||
To get additional output from the `gh-pages` script, set `NODE_DEBUG=gh-pages`. For example: | ||
```shell | ||
NODE_DEBUG=gh-pages npm run deploy | ||
``` | ||
## Dependencies | ||
Note that this plugin requires Git 1.7.6 or higher (because it uses the `--exit-code` option for `git ls-remote`). If you'd like to see this working with earlier versions of Git, please [open an issue](https://github.com/tschaub/gh-pages/issues). | ||
Note that this plugin requires Git 1.9 or higher (because it uses the `--exit-code` option for `git ls-remote`). If you'd like to see this working with earlier versions of Git, please [open an issue](https://github.com/tschaub/gh-pages/issues). | ||
[![Current Status](https://secure.travis-ci.org/tschaub/gh-pages.svg?branch=master)](https://travis-ci.org/tschaub/gh-pages) |
var chai = require('chai'); | ||
var tmp = require('tmp'); | ||
var path = require('path'); | ||
var fs = require('fs-extra'); | ||
var Git = require('../lib/git'); | ||
var compare = require('dir-compare').compareSync; | ||
/** | ||
* Turn off maxListeners warning during the tests | ||
* See: https://nodejs.org/docs/latest/api/events.html#events_emitter_setmaxlisteners_n | ||
*/ | ||
require('events').EventEmitter.prototype._maxListeners = 0; | ||
@@ -7,3 +17,2 @@ /** @type {boolean} */ | ||
/** | ||
@@ -14,1 +23,94 @@ * Chai's assert function configured to include stacks on failure. | ||
exports.assert = chai.assert; | ||
var fixtures = path.join(__dirname, 'integration', 'fixtures'); | ||
function mkdtemp() { | ||
return new Promise(function(resolve, reject) { | ||
tmp.dir({unsafeCleanup: true}, function(err, tmpPath) { | ||
if (err) { | ||
return reject(err); | ||
} | ||
resolve(tmpPath); | ||
}); | ||
}); | ||
} | ||
function relay(value) { | ||
return function() { | ||
return value; | ||
}; | ||
} | ||
function setupRemote(fixtureName, options) { | ||
options = options || {}; | ||
var branch = options.branch || 'gh-pages'; | ||
var userEmail = (options.user && options.user.email) || 'user@email.com'; | ||
var userName = (options.name && options.user.name) || 'User Name'; | ||
return mkdtemp() | ||
.then(function(remote) { | ||
return new Git(remote).exec('init', '--bare').then(relay(remote)); | ||
}) | ||
.then(function(remote) { | ||
return mkdtemp() | ||
.then(function(clone) { | ||
const fixturePath = path.join(fixtures, fixtureName, 'remote'); | ||
return fs.copy(fixturePath, clone).then(relay(new Git(clone))); | ||
}) | ||
.then(function(git) { | ||
return git.init(); | ||
}) | ||
.then(function(git) { | ||
return git.exec('config', 'user.email', userEmail); | ||
}) | ||
.then(function(git) { | ||
return git.exec('config', 'user.name', userName); | ||
}) | ||
.then(function(git) { | ||
return git.exec('checkout', '--orphan', branch); | ||
}) | ||
.then(function(git) { | ||
return git.add('.'); | ||
}) | ||
.then(function(git) { | ||
return git.commit('Initial commit'); | ||
}) | ||
.then(function(git) { | ||
var url = 'file://' + remote; | ||
return git.exec('push', url, branch).then(relay(url)); | ||
}); | ||
}); | ||
} | ||
function assertContentsMatch(dir, url, branch) { | ||
return mkdtemp() | ||
.then(function(root) { | ||
var clone = path.join(root, 'repo'); | ||
var options = {git: 'git', remote: 'origin', depth: 1}; | ||
return Git.clone(url, clone, branch, options); | ||
}) | ||
.then(function(git) { | ||
var comparison = compare(dir, git.cwd, {excludeFilter: '.git'}); | ||
if (comparison.same) { | ||
return true; | ||
} else { | ||
var message = comparison.diffSet | ||
.map(function(entry) { | ||
var state = { | ||
equal: '==', | ||
left: '->', | ||
right: '<-', | ||
distinct: '<>' | ||
}[entry.state]; | ||
var name1 = entry.name1 ? entry.name1 : '<none>'; | ||
var name2 = entry.name2 ? entry.name2 : '<none>'; | ||
return [name1, state, name2].join(' '); | ||
}) | ||
.join('\n'); | ||
throw new Error('Directories do not match:\n' + message); | ||
} | ||
}); | ||
} | ||
exports.setupRemote = setupRemote; | ||
exports.assertContentsMatch = assertContentsMatch; |
@@ -1,2 +0,1 @@ | ||
/* eslint-env mocha */ | ||
var path = require('path'); | ||
@@ -9,3 +8,2 @@ | ||
describe('util', function() { | ||
var files; | ||
@@ -53,3 +51,2 @@ beforeEach(function() { | ||
describe('uniqueDirs', function() { | ||
it('gets a list of unique directory paths', function() { | ||
@@ -75,6 +72,27 @@ // not comparing order here, so we sort both | ||
it('gets a list of unique directories on absolute paths', function() { | ||
var absoluteFiles = files.map(function(path) { | ||
return '/' + path; | ||
}); | ||
// not comparing order here, so we sort both | ||
var got = util.uniqueDirs(absoluteFiles).sort(); | ||
var expected = [ | ||
'/', | ||
'/a1', | ||
'/a2', | ||
path.join('/a1', 'b1'), | ||
path.join('/a1', 'b1', 'c1'), | ||
path.join('/a1', 'b1', 'c2'), | ||
path.join('/a1', 'b2'), | ||
path.join('/a1', 'b2', 'c1'), | ||
path.join('/a1', 'b2', 'c2'), | ||
path.join('/a2', 'b1') | ||
].sort(); | ||
assert.deepEqual(got, expected); | ||
}); | ||
}); | ||
describe('dirsToCreate', function() { | ||
it('gets a sorted list of directories to create', function() { | ||
@@ -98,5 +116,3 @@ var got = util.dirsToCreate(files); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
48312
33
986
333
7
3
+ Addedbase64url@^2.0.0
+ Addedfs-extra@^3.0.1
+ Addedasync@2.1.4(transitive)
+ Addedbase64url@2.0.0(transitive)
+ Addedfs-extra@3.0.1(transitive)
+ Addedgraceful-fs@4.1.11(transitive)
+ Addedjsonfile@3.0.1(transitive)
+ Addeduniversalify@0.1.2(transitive)
- Removedq@1.4.1
- Removedq-io@1.13.2
- Removedasync@2.1.2(transitive)
- Removedcollections@0.2.2(transitive)
- Removedgraceful-fs@4.1.10(transitive)
- Removedmime@1.6.0(transitive)
- Removedmimeparse@0.1.4(transitive)
- Removedq@1.4.1(transitive)
- Removedq-io@1.13.2(transitive)
- Removedqs@1.2.2(transitive)
- Removedurl2@0.0.0(transitive)
- Removedweak-map@1.0.0(transitive)
Updatedasync@2.1.4
Updatedgraceful-fs@4.1.11