-
Notifications
You must be signed in to change notification settings - Fork 190
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from markdalgleish/add-publish-method
Extract publish task logic from grunt-gh-pages.
- Loading branch information
Showing
3 changed files
with
369 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,201 @@ | ||
var path = require('path'); | ||
|
||
var Q = require('q'); | ||
var wrench = require('wrench'); | ||
var _ = require('lodash'); | ||
var grunt = require('grunt'); | ||
|
||
/** | ||
* Generate promises for spawned git commands. | ||
*/ | ||
exports.git = require('./git'); | ||
var pkg = require('../package.json'); | ||
var git = require('./git'); | ||
|
||
var copy = require('./util').copy; | ||
|
||
function getCacheDir() { | ||
return '.gh-pages'; | ||
} | ||
|
||
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.origin.url (task must either be run in a ' + | ||
'git repository with a configured origin remote or must be ' + | ||
'configured with the "repo" option).')); | ||
}); | ||
} | ||
|
||
function getRepo(options) { | ||
if (options.repo) { | ||
return Q.resolve(options.repo); | ||
} else { | ||
return getRemoteUrl(process.cwd(), 'origin'); | ||
} | ||
} | ||
|
||
|
||
exports.publish = function publish(config, done) { | ||
var defaults = { | ||
add: false, | ||
git: 'git', | ||
clone: getCacheDir(), | ||
dotfiles: false, | ||
branch: 'gh-pages', | ||
remote: 'origin', | ||
base: process.cwd(), | ||
src: '**/*', | ||
only: '.', | ||
push: true, | ||
message: 'Updates', | ||
silent: false, | ||
logger: function(){} | ||
}; | ||
|
||
// override defaults with any task options | ||
var options = _.extend({}, defaults, config); | ||
|
||
if (!grunt.file.isDir(options.base)) { | ||
return done(new Error('The "base" option must be an existing directory')); | ||
} | ||
|
||
var files = grunt.file.expand({ | ||
filter: 'isFile', | ||
cwd: options.base, | ||
dot: options.dotfiles | ||
}, options.src); | ||
|
||
if (!Array.isArray(files) || files.length === 0) { | ||
return done(new Error('Files must be provided in the "src" property.')); | ||
} | ||
|
||
var only = grunt.file.expand({cwd: options.base}, options.only); | ||
|
||
function log(message) { | ||
if (!options.silent) { | ||
options.logger(message); | ||
} | ||
} | ||
|
||
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 `grunt gh-pages-clean` 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() { | ||
log('Copying files'); | ||
return copy(files, options.base, 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() { | ||
done(); | ||
}, function(error) { | ||
if (options.silent) { | ||
error = new Error( | ||
'Unspecified error (run without silent option for detail)'); | ||
} | ||
done(error); | ||
}); | ||
}; | ||
|
||
exports.clean = function clean() { | ||
wrench.rmdirSyncRecursive(getCacheDir(), true); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
var path = require('path'); | ||
|
||
var async = require('async'); | ||
var fs = require('graceful-fs'); | ||
var Q = require('q'); | ||
|
||
|
||
/** | ||
* Generate a list of unique directory paths given a list of file paths. | ||
* @param {Array.<string>} files List of file paths. | ||
* @return {Array.<string>} List of directory paths. | ||
*/ | ||
var uniqueDirs = exports.uniqueDirs = function(files) { | ||
var dirs = {}; | ||
files.forEach(function(filepath) { | ||
var parts = path.dirname(filepath).split(path.sep); | ||
var partial = parts[0]; | ||
dirs[partial] = true; | ||
for (var i = 1, ii = parts.length; i < ii; ++i) { | ||
partial = path.join(partial, parts[i]); | ||
dirs[partial] = true; | ||
} | ||
}); | ||
return Object.keys(dirs); | ||
}; | ||
|
||
|
||
/** | ||
* Sort function for paths. Sorter paths come first. Paths of equal length are | ||
* sorted alphanumerically in path segment order. | ||
* @param {string} a First path. | ||
* @param {string} b Second path. | ||
* @return {number} Comparison. | ||
*/ | ||
var byShortPath = exports.byShortPath = function(a, b) { | ||
var aParts = a.split(path.sep); | ||
var bParts = b.split(path.sep); | ||
var aLength = aParts.length; | ||
var bLength = bParts.length; | ||
var cmp = 0; | ||
if (aLength < bLength) { | ||
cmp = -1; | ||
} else if (aLength > bLength) { | ||
cmp = 1; | ||
} else { | ||
var aPart, bPart; | ||
for (var i = 0; i < aLength; ++i) { | ||
aPart = aParts[i]; | ||
bPart = bParts[i]; | ||
if (aPart < bPart) { | ||
cmp = -1; | ||
break; | ||
} else if (aPart > bPart) { | ||
cmp = 1; | ||
break; | ||
} | ||
} | ||
} | ||
return cmp; | ||
}; | ||
|
||
|
||
/** | ||
* Generate a list of directories to create given a list of file paths. | ||
* @param {Array.<string>} files List of file paths. | ||
* @return {Array.<string>} List of directory paths ordered by path length. | ||
*/ | ||
var dirsToCreate = exports.dirsToCreate = function(files) { | ||
return uniqueDirs(files).sort(byShortPath); | ||
}; | ||
|
||
|
||
/** | ||
* Copy a file. | ||
* @param {Object} obj Object with src and dest properties. | ||
* @param {function(Error)} callback Callback | ||
*/ | ||
var copyFile = exports.copyFile = function(obj, callback) { | ||
var called = false; | ||
function done(err) { | ||
if (!called) { | ||
called = true; | ||
callback(err); | ||
} | ||
} | ||
|
||
var read = fs.createReadStream(obj.src); | ||
read.on('error', function(err) { | ||
done(err); | ||
}); | ||
|
||
var write = fs.createWriteStream(obj.dest); | ||
write.on('error', function(err) { | ||
done(err); | ||
}); | ||
write.on('close', function(ex) { | ||
done(); | ||
}); | ||
|
||
read.pipe(write); | ||
}; | ||
|
||
|
||
/** | ||
* Make directory, ignoring errors if directory already exists. | ||
* @param {string} path Directory path. | ||
* @param {function(Error)} callback Callback. | ||
*/ | ||
function makeDir(path, callback) { | ||
fs.mkdir(path, function(err) { | ||
if (err) { | ||
// check if directory exists | ||
fs.stat(path, function(err2, stat) { | ||
if (err2 || !stat.isDirectory()) { | ||
callback(err); | ||
} else { | ||
callback(); | ||
} | ||
}); | ||
} else { | ||
callback(); | ||
} | ||
}); | ||
} | ||
|
||
|
||
/** | ||
* Copy a list of files. | ||
* @param {Array.<string>} files Files to copy. | ||
* @param {string} base Base directory. | ||
* @param {string} dest Destination directory. | ||
* @return {Promise} A promise. | ||
*/ | ||
var copy = 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 | ||
}); | ||
destFiles.push(target); | ||
}); | ||
|
||
async.eachSeries(dirsToCreate(destFiles), makeDir, function(err) { | ||
if (err) { | ||
return deferred.reject(err); | ||
} | ||
async.each(pairs, copyFile, function(err) { | ||
if (err) { | ||
return deferred.reject(err); | ||
} else { | ||
return deferred.resolve(); | ||
} | ||
}); | ||
}); | ||
|
||
return deferred.promise; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters