mirror of
https://github.com/scratchfoundation/gh-pages.git
synced 2024-11-28 18:25:38 -05:00
Extract publish task logic from grunt-gh-pages
+ lint fix for git.js
This commit is contained in:
parent
c4c1c2619f
commit
63dd7b5bc1
3 changed files with 369 additions and 5 deletions
203
lib/index.js
203
lib/index.js
|
@ -1,6 +1,201 @@
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var Q = require('q');
|
||||||
|
var wrench = require('wrench');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var grunt = require('grunt');
|
||||||
|
|
||||||
|
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) {
|
||||||
* Generate promises for spawned git commands.
|
var defaults = {
|
||||||
*/
|
add: false,
|
||||||
exports.git = require('./git');
|
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);
|
||||||
|
};
|
||||||
|
|
164
lib/util.js
Normal file
164
lib/util.js
Normal file
|
@ -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;
|
||||||
|
};
|
|
@ -31,7 +31,12 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"q": "~1.0.1",
|
"q": "~1.0.1",
|
||||||
"q-io": "~1.11.0"
|
"q-io": "~1.11.0",
|
||||||
|
"graceful-fs": "2.0.1",
|
||||||
|
"async": "0.2.9",
|
||||||
|
"wrench": "1.5.1",
|
||||||
|
"lodash": "~2.4.1",
|
||||||
|
"grunt": "~0.4.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"glob": "~3.2.9",
|
"glob": "~3.2.9",
|
||||||
|
|
Loading…
Reference in a new issue