Skip to content

Instantly share code, notes, and snippets.

@jrburke
Created June 29, 2012 22:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jrburke/3021061 to your computer and use it in GitHub Desktop.
Save jrburke/3021061 to your computer and use it in GitHub Desktop.
volofile
/*jslint regexp: true */
/*global define, console, process */
var connect = require( "connect" ),
crypto = require('crypto'),
fs = require('fs'),
path = require('path'),
buildDir = 'www-built',
pagesDir = 'www-ghpages';
module.exports = {
// Installs twitter bootstrap
install_twbootstrap: {
run: function (d, v, namedArgs, nowrap) {
var tempName = 'tempbootstrap',
jsNameRegExp = /bootstrap-(\w*)\.js$/,
q = v.require('q');
function finished_message() {
console.log('\nTwitter Bootstrap installed.\n' +
'Add this css in your <head>:\n' +
'<link rel="stylesheet"' +
' href="css/bootstrap.css">\n\n' +
'If you want the responsive styles, ' +
'add this also:\n' +
'<link rel="stylesheet"' +
' href="css/bootstrap-responsive.css">\n');
if(nowrap) {
console.log('You also need to include any ' +
'js for components you use, like so:\n' +
'<script src="js/lib/bootstrap/button.js">' +
'</script>');
}
else {
// Double-quoting the require command is
// necessary so that require.js doesn't strip
// it out
console.log('You also need to install any ' +
'js components by just requiring ' +
'them, like so:\n' +
"require(\"bootstrap/button\");");
}
}
v.command('create', tempName, 'twitter/bootstrap')
.then(function () {
//Move the JS to the right location.
var jsFiles = v.getFilteredFileList(tempName + '/js', /\.js$/, /js\/tests\//),
promises = [];
jsFiles.forEach(function (file) {
//Pull off the name part from bootstrap-name.js pattern.
var match = jsNameRegExp.exec(file),
name, destName, damd;
if (!match) {
return;
}
name = jsNameRegExp.exec(file)[1];
destName = 'www/js/lib/bootstrap/' + name + '.js';
damd = q.defer();
v.copyFile(file, destName);
if(nowrap != 'nowrap') {
//Convert the file to AMD style
v.require('./commands/amdify').run.apply(amdify, [damd, v, {
depends: 'jquery'
}, destName]);
}
promises.push(damd);
});
//Wait for all the amdify calls to finish.
return q.all(promises);
})
.then(function () {
//Copy the images over.
v.copyDir(tempName + '/img', 'www/img');
//Copy the less files.
v.copyDir(tempName + '/less', 'tools/less');
//Compile the CSS.
return v.command('less').then(function () {
v.rm(tempName);
finished_message();
});
})
.then(d.resolve, d.reject);
}
},
install_xtags: {
run: function(d, v, namedArgs) {
var tempName = 'tempxtags',
jsNameRegExp = /\/([^\/]*)\.js$/,
q = v.require('q');
function finished_message() {
console.log('x-tags is installed.\n' +
'You need to include the js and css for ' +
'each component you want to use, like so:\n\n' +
'<link rel="stylesheet"' +
' href="css/lib/x-tag/accordion.css">\n' +
'<script src="js/lib/x-tag/accordion.js></script>');
}
v.command('create', tempName, 'csuwldcat/x-tag/gh-pages')
.then(function() {
//Move the JS to the right location.
v.copyFile(tempName + '/x-tag.js', 'www/js/lib/x-tag.js');
var jsFiles = v.getFilteredFileList(tempName + '/elements',
/\.js$/);
v.mkdir('www/js/lib/x-tag');
jsFiles.forEach(function(file) {
var matches = file.match(jsNameRegExp);
if(!matches) {
return;
}
var damd = q.defer();
var path = 'www/js/lib/x-tag/' + matches[1] + '.js';
v.copyFile(file, path);
});
//Copy the css
var files = v.getFilteredFileList(tempName + '/elements',
/\.css$/);
var basenameRegExp = /\/([^\/]*.css)/;
v.mkdir('www/css/lib/x-tag');
files.forEach(function(file) {
var matches = file.match(basenameRegExp);
if(!matches) {
return;
}
v.copyFile(file, 'www/css/lib/x-tag/' + matches[1]);
});
v.rm(tempName);
finished_message();
})
.then(d.resolve, d.reject);
}
},
//Builds the JS and CSS into one file each. If you want to do
//dynamic loading of scripts, pass -dynamic to the build, and
//require.js will be used to load scripts.
build: {
flags: {
//Does not print the build output.
'q': 'quiet',
//Uses dynamic loading via require.js instead of building
//all the modules in with almond.
'dynamic': 'dynamic'
},
run: function (d, v, namedArgs) {
var q = v.require('q');
q.call(function () {
//Remove the old dir
v.rm(buildDir);
if (!namedArgs.dynamic) {
//Copy the directory for output.
v.copyDir('www', buildDir);
//Remove the js dir from the built area, will be
//replaced by an optimized app.js
v.rm(buildDir + '/js');
//Do the CSS optimization
return v.spawn('node', ['tools/r.js', '-o',
'cssIn=www/css/app.css',
'out=' + buildDir + '/css/app.css'], {
useConsole: !namedArgs.quiet
});
}
return undefined;
})
.then(function () {
//JS go time
var optimize = namedArgs.optimize || 'uglify';
if (namedArgs.dynamic) {
//Still use require.js to load the app.js file.
return v.spawn('node', ['tools/r.js', '-o',
'appDir=www',
'baseUrl=js/lib',
'paths.app=../app',
'name=app',
'dir=' + buildDir,
'optimize=' + optimize], {
useConsole: !namedArgs.quiet
});
} else {
//The all-in-one option.
return v.spawn('node', ['tools/r.js', '-o',
'baseUrl=www/js/lib',
'paths.app=../app',
'paths.almond=../../../tools/almond',
'name=almond',
'include=app',
'out=' + buildDir + '/js/app.js',
'optimize=' + optimize], {
useConsole: !namedArgs.quiet
});
}
})
.then(function (buildOutput) {
//Remove all the CSS except for the app.css, since it
//inlines all the other ones.
v.getFilteredFileList(buildDir + '/css').forEach(function (path) {
if (!/app\.css$/.test(path)) {
v.rm(path);
}
});
//If almond is in use, it is built into app.js, so need
//to update the script tag to just load app.js instead.
if (!namedArgs.dynamic) {
var indexName = buildDir + '/index.html',
contents = v.read(indexName),
scriptRegExp = /(<script[^>]+data-main="[^"]+"[^>]+)(src="[^"]+")([^>]+>\s*<\/script>)/;
contents = contents.replace(scriptRegExp,
function (match, pre, script, post) {
return pre + 'src="js/app.js"' + post;
});
v.write(indexName, contents);
}
return buildOutput;
})
.then(function (buildOutput) {
d.resolve(buildOutput);
}, d.reject);
}
},
//Generates an SHA1 digest that represents the contents of the
//a directory. Call it like so: "volo digest dir=path/to/directory"
digest: {
validate: function (namedArgs) {
var dir = namedArgs.dir;
if (!dir) {
return new Error('Please specify a target directory for ' +
'the digest');
}
if (!path.existsSync(dir)) {
return new Error('Target directory for digest does ' +
'not exist: ' + dir);
}
return undefined;
},
run: function (d, v, namedArgs) {
var dir = namedArgs.dir,
files = v.getFilteredFileList(dir),
q = v.require('q'),
digests = [],
i = 0;
function getDigest(fileName) {
var shaSum = crypto.createHash('sha1'),
d = q.defer(),
stream = fs.ReadStream(fileName);
stream.on('data', function(data) {
shaSum.update(data);
});
stream.on('end', function() {
d.resolve(shaSum.digest('base64'));
});
return d.promise;
}
function digestFile(fileName) {
getDigest(fileName).then(function (digest) {
var shaSum;
digests[i] = digest;
i += 1;
if (i < files.length) {
digestFile(files[i]);
} else {
//All done, now generate the final digest,
//using the combination of the other digests
shaSum = crypto.createHash('sha1');
shaSum.update(digests.join(','));
d.resolve(shaSum.digest('base64'));
}
});
}
digestFile(files[0]);
}
},
ghdeploy: {
run: function (d, v, namedArgs) {
var spawnOptions = {
useConsole: !namedArgs.quiet
},
q = v.require('q'),
github = v.require('./lib/github'),
authInfo, repoName, hasGhPages;
q.call(function () {
//First check if there is already a repo
if (!v.exists(buildDir)) {
throw new Error('Run build or appcache first to generate a deploy target in "www-built".');
}
//If already have gh-pages dir, go to next step.
if (v.exists(pagesDir)) {
return;
}
//Figure out if already in a github repo.
return v.require('./lib/github/auth').fetch({ v: v })
.then(function (info) {
authInfo = info;
//Suggest the current directory name as the repo name.
repoName = path.basename(process.cwd());
return v.prompt(authInfo.user +
', name of github repo [' +
repoName + ']:');
})
.then(function (promptRepoName) {
var dfd = q.defer();
if (promptRepoName) {
repoName = promptRepoName;
}
//First check to see if it exists.
github('repos/' + authInfo.user + '/' + repoName)
.then(function (data) {
var sshUrl = data.ssh_url;
//Repo exists, see if there is a gh-pages repo
//already
github('repos/' + authInfo.user + '/' + repoName + '/branches')
.then(function (data) {
if (data && data.length) {
hasGhPages = data.some(function (branch) {
return branch.name === 'gh-pages';
});
}
dfd.resolve(sshUrl);
}, dfd.reject);
}, function (err) {
if (err.response.statusCode === 404) {
github('user/repos', {
method: 'POST',
token: authInfo.token,
content: {
name: repoName
}
})
.then(function (data) {
dfd.resolve(data.ssh_url);
}, function (err) {
dfd.reject(err);
});
} else {
dfd.reject(err);
}
});
return dfd.promise;
})
.then(function (sshUrl) {
//Set up .git.
v.mkdir(pagesDir);
//Set up the gh-pages repo in the built area.
return v.withDir(pagesDir, function () {
if (hasGhPages) {
//Set up the git repo locally. Just commit a file
//to get the repo prepped and sent to GitHub.
return v.sequence([
['git', 'init'],
['git', 'remote', 'add', 'origin', sshUrl],
//This step mandated by:
//http://help.github.com/pages/#project_pages_manually
['git', 'symbolic-ref', 'HEAD', 'refs/heads/gh-pages'],
[v, 'rm', '.git/index'],
['git', 'clean', '-fdx'],
['git', 'pull', 'origin', 'gh-pages']
], spawnOptions);
} else {
//Set up the git repo locally. Just commit a file
//to get the repo prepped and sent to GitHub.
return v.sequence([
['git', 'init'],
['git', 'remote', 'add', 'origin', sshUrl],
//This step mandated by:
//http://help.github.com/pages/#project_pages_manually
['git', 'symbolic-ref', 'HEAD', 'refs/heads/gh-pages'],
[v, 'rm', '.git/index'],
['git', 'clean', '-fdx'],
[v, 'write', 'index.html', 'Setting up pages...'],
['git', 'add', 'index.html'],
['git', 'commit', '-m', 'Create branch.'],
['git', 'push', 'origin', 'gh-pages']
], spawnOptions);
}
});
});
})
.then(function () {
var message = namedArgs.m;
if (!message) {
message = 'Deploy';
}
//Clean up www-ghpages first, but keep .git
if (v.exists(pagesDir)) {
fs.readdirSync(pagesDir).forEach(function (name) {
if (name !== '.git') {
v.rm(pagesDir + '/' + name);
}
});
}
//Copy the contents of www-built to www-ghpages
//Copy the directory for output.
v.copyDir(buildDir, pagesDir);
//Trigger update to origin.
return v.withDir(pagesDir, function () {
return v.sequence([
//Add any new files
['git', 'add', '.'],
//Remove any files from git that are not on on disk
['git', 'add', '-u'],
['git', 'commit', '-m', message],
['git', 'push', 'origin', 'gh-pages']
], spawnOptions);
});
})
.then(function () {
if (repoName) {
return 'GitHub Pages is set up. Check http://' +
authInfo.user + '.github.com/' + repoName +
'/ in about 10-15 minutes.';
}
})
.then(d.resolve, d.reject);
}
},
//Runs less on the .less files in tools/less to generate the CSS files.
less: function (d, v, namedArgs) {
v.require('q').all([
v.exec('node tools/oneless.js tools/less/bootstrap.less > www/css/bootstrap.css'),
v.exec('node tools/oneless.js tools/less/responsive.less > www/css/bootstrap-responsive.css')
])
.then(function () {
d.resolve();
})
.fail(d.reject);
},
appcache: function (d, v, namedArgs) {
var hasBuilt = v.exists(buildDir);
v.command('build')
.then(function () {
var manifest = v.read('tools/manifest.appcache'),
master = v.read(buildDir + '/index.html'),
appFiles;
appFiles = v.getFilteredFileList(buildDir);
appFiles = appFiles.map(function (file) {
var start = file.indexOf('/' + buildDir + '/');
start = (start !== -1) ? (start + 11) : 0;
return file.substr(start, file.length);
});
master = master
.replace(/<html\s?/, '<html manifest="manifest.appcache" ')
.replace(/manifest\.appcache"\s>/, 'manifest.appcache">');
v.write(buildDir + '/index.html', master);
return v.command('digest', 'dir=' + buildDir)
.then(function (stamp) {
manifest = v.template(manifest, {
files : appFiles.join('\n'),
stamp : stamp
});
v.write(buildDir + '/manifest.appcache', manifest);
});
})
.then(function () {
//Inform the user of the right mime type, but only do it if
//there was not a previous build done.
d.resolve(hasBuilt ? '': 'Be sure to set the mime type for ' +
'.appcache files to be: text/cache-manifest');
})
.fail(d.reject);
},
serve: function( d, v, namedArgs ) {
var port = 8086;
var base = process.cwd();
var middleware = [
connect.static( base ),
connect.directory( base )
];
connect.logger.format( "WebGameStub", ("[D] server :method :url :status " +
":res[content-length] - :response-time ms" ));
middleware.unshift( connect.logger( "WebGameStub" ) );
console.log( "starting web server on port " + port );
connect.apply( null, middleware ).listen( port );
}
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment