Created
June 29, 2012 22:26
-
-
Save jrburke/3021061 to your computer and use it in GitHub Desktop.
volofile
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
/*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