Skip to content

Instantly share code, notes, and snippets.

@cmcculloh
Last active December 20, 2015 14:29
Show Gist options
  • Save cmcculloh/6146701 to your computer and use it in GitHub Desktop.
Save cmcculloh/6146701 to your computer and use it in GitHub Desktop.
Sample of a typical requirejs single page nodeapp config
/*global module:false*/
/*
Allow grunt to utilize Git for build management
This is kind of like a miniature, built-in version of GitScripts https://github.com/cmcculloh/GitScripts
*/
var GitScripts = function(g){
var grunt = g;
return {
commands:{
"checkout master": "git checkout master",
"checkout": "git checkout",
"merge": "git merge",
"hard reset": "git reset --hard",
"reset": "git reset",
"force delete": "git branch -D",
"delete": "git branch -d",
"create": "git checkout -b"
},
hello: function(){
//instead of overwriting the shell object, should extend it
grunt.config("shell.echo_test", {command: "echo test", stdout: true});
},
run: function(){
grunt.task.run("shell");
},
clear: function(){
grunt.config("shell", {});
},
checkoutMaster: function(){
grunt.config("shell.checkout_master",{command: this.commands["checkout master"], stdout: true});
},
checkout: function(what){
grunt.config('shell.checkout', {command: this.commands['checkout'] + " " + what, stdout: true});
},
merge: function(branchlist, flags){
if(!flags){
flags = "";
}
for(var i=0, ii=branchlist.length; i<ii; i++){
grunt.config("shell.merge" + i, {command: this.commands.merge + " " + flags + " origin/" + branchlist[i], stdout: true});
}
},
reset: function(){
grunt.config("shell.reset", {command: this.commands['hard reset'], stdout: true});
},
del: function(branch, safe){
if(safe === undefined){
safe = true;
}
if(safe){
grunt.config("shell.delete", {command:this.commands["delete"] + " " + branch, stdout: true});
}else{
grunt.config("shell.delete", {command:this.commands["force delete"] + " " + branch, stdout: true});
}
},
create: function(branch, from){
if(from === undefined){
from = "master";
}
grunt.config("shell.create", {command:this.commands["create"] + " " + branch + " " + from, stdout: true});
},
deploy: function(){
grunt.config("shell.deploy", {command:"stackato update --no-prompt", stdout:true});
}
};
};
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib');
grunt.loadNpmTasks('grunt-testem');
grunt.loadNpmTasks('grunt-shell');
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
watch: {
files: ['grunt.js', 'public/javascripts/**/*.js'],
tasks: 'lint testem'
},
jshint: {
uses_defaults: ['public/javascripts/**/*.js'],
options: {
curly: false,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
boss: true,
eqnull: true,
browser: true,
devel: true,
laxcomma: true,
globals: {
define: true,
require: true,
$: true,
_: true,
io: true,
google: true,
tinymce: true
}
},
with_overrides: {
options: {
browser: false,
node: true
},
globals: {
__dirname: true,
module: true,
define: false,
require: false,
$: false,
_: false
},
files: {
src: ['Gruntfile.js', 'app.js', 'lib/*.js']
}
}
},
requirejs: {
combine: {
options: {
appDir: 'public/',
baseUrl: 'vendor',
dir: 'public-optimized/',
optimize: 'uglify2',/* 'none', */
optimizeCss: 'none',
mainConfigFile: 'public/javascripts/app/main.js',
generateSourceMaps: true,
preserveLicenseComments: false,
paths: {
config: 'empty:'
},
modules: [
{ name: 'app/main' }
]
}
}
},
shell: {}
});
// Helper to execute a file and continually display its output
grunt.exec_file = function (file, args, options, done) {
var cmd = require('child_process').execFile(file, args, options);
cmd.stderr.on('data', function(data) {
grunt.log.write(data.toString());
});
cmd.stdout.on('data', function(data) {
grunt.log.write(data.toString());
});
cmd.on('exit', function(error) {
done(!error);
});
};
// Helper to execute a command and continually display its output
grunt.exec_cmd = function (cmd, args, options, done) {
var proc = require('child_process').exec(cmd, args, options);
proc.stderr.on('data', function(data) {
grunt.log.write(data.toString());
});
proc.stdout.on('data', function(data) {
grunt.log.write(data.toString());
});
proc.on('exit', function(error) {
done(!error);
});
};
// Launch the project with an optional argument for Express environment
grunt.registerTask('server', 'Runs the node.js app for this project', function () {
var done = this.async();
var args = '';
var useTrace = false;
process.env.HOST = '';
process.env.NODE_ENV = '';
for(var i=0; i<this.args.length; i++){
//detect named environment flags and set the NODE_ENV var based on them (maps directly to config/dev.js or config/qa1s1.js or config/edge.js etc)
if(this.args[i].match(/qa1|qa2|prod|dev|edge/)){
process.env.NODE_ENV = this.args[i];
}
//since there are multiple qa environments, determine if the user paseed the "qa" flag to indicate they are in a "qa like" environment and use the global settings from the qa.js config file. Any config grabbed based on the NODE_ENV var set about will override this
if(this.args[i].match(/^qa$/)){
process.env.HOST = this.args[i];
}
if(this.args[i] === 'tracegl' || this.args[i] === 'trace') {
useTrace = true;
}
args += this.args[i] + ' ';
}
if(useTrace){
grunt.exec_cmd('node ~/tracegl ' + __dirname + '/' + 'app.js -nolib -tgt:4000 ' + args, this.args, {}, done);
}else{
//pass -q to silence debug messages...
//utilize forever to restart node server when it crashes
grunt.exec_cmd('forever start ' + __dirname + '/app.js', this.args, {}, done);
//grunt.exec_file( __dirname + '/' + 'app.js', this.args, {}, done);
}
});
// Default task. Stamp the project, run jshint to validate, then min/merge everything together
grunt.registerTask('default', ['stamp', 'jshint', 'requirejs']);
// append abbreviated githash to the top of version.txt so you can always see exactly which build is being served
grunt.registerTask('stamp', 'Stamp', function stamp() {
grunt.config("shell", {});
grunt.config('shell.stamp', {command: 'git log -1 --abbrev=7 --format=oneline | cat - version.txt > /tmp/out && mv /tmp/out version.txt'});
grunt.config('shell.time', {command: 'git log -1 --format="<br><b>%ci</b>:" | cat - version.txt > /tmp/out && mv /tmp/out version.txt'});
// grunt.config('shell.newline', {command: 'echo "<br>\n" | cat - version.txt > /tmp/out && mv /tmp/out version.txt', stdout: true});
grunt.task.run("shell");
});
grunt.registerTask('edge', 'Calls server with edge settings', function () {
grunt.task.run('server:https:qa:edge-local');
});
// Debug task. Sets "devel" to true to not warn about console statements
grunt.registerTask('development', 'Development Task', function(){
grunt.config("jshint.options.devel", true);
grunt.config("requirejs.combine.options.optimize", "none");
grunt.task.run('default');
});
/**
* build-branch
*
* This will create a branch based on an arbitrary list of branches. This allows managing branches to be included in DEV, QA, Stage and Prod more easily.
*
* Do not call this directly from command line. Instead, register a task that calls this after populating a "shellopts" grunt config:
*
grunt.registerTask('build-edge', 'Build edge', function(){
grunt.config('shellopts',
{
branch: 'edge',//this indicates the branch that will be created once this task runs
list: grunt.buildList['2013-06'].concat('edge-stackato-config'),//specify the .net build you want to use for your buildList and then concat your stackato-config branch onto the end. You can specify multiple .net build by separating them with commas if you want...
from: 'master'//starting point. You can specify any branch as a starting point. I almost always use master, but, this could be useful for a big project that wants to use another branch as its common ancestor
}
);
grunt.task.run('build-branch');
});
*
*/
grunt.registerTask('build-branch', 'Builds a branch from an arbitrary list', function(){
var gitScripts = new GitScripts(grunt);
gitScripts.clear();
if(grunt.config('shellopts.from') === undefined){
gitScripts.checkoutMaster();
}else{
gitScripts.checkout(grunt.config('shellopts.from'));
}
gitScripts.del(grunt.config('shellopts.branch'), false);
gitScripts.create(grunt.config('shellopts.branch'), grunt.config('shellopts.from'));
gitScripts.merge(grunt.config('shellopts.list'), "--no-ff");
gitScripts.run();
grunt.task.run('default');
});
/*
Ideas: make it so this list can be managed from the browser, even just the stackato-deploy part
*/
grunt.buildList = {
'2013-05': ['FTR---734---hide-nav-bar-on-page-load'],
'2013-06': ['FTR---734---hide-nav-bar-on-page-load',
'BUG---793---edit-message-success-wrong'],
'2013-07': ['FTR---734---hide-nav-bar-on-page-load',
'BUG---793---edit-message-success-wrong',
'FTR---812---support-reload-at-any-url'],
'experimental': ['FTR---734---hide-nav-bar-on-page-load',
'BUG---793---edit-message-success-wrong',
'bootstrap-3',
'nginx-support']
};
/*
Each build target is defined in this way. To create a new build target, just copy the below and edit accordingly.
You will need to make a stackato-config branch for any new build targets. This branch should NEVER be merged back into master because
prod has its own stackato configs that would be overwritten by this. Which is why you can just run "build-qa2s2" and then merge the results
of that into master (you need to use the build-new-master task instead)
*/
grunt.registerTask('build-edge', 'Build edge', function(){
grunt.config('shellopts',
{
branch: 'edge',//this indicates the branch that will be created once this task runs
list: grunt.buildList['2013-06'].concat('edge-stackato-config'),//specify the .net build you want to use for your buildList and then concat your stackato-config branch onto the end. You can specify multiple .net build by separating them with commas if you want...
from: 'master'//starting point. You can specify any branch as a starting point. I almost always use master, but, this could be useful for a big project that wants to use another branch as its common ancestor
}
);
grunt.task.run('build-branch');
});
/*
When a build target is promoted to prod, you will want to merge it in with master. Instead of checking out master and doing this manually, just
run 'build-new-master' and use that build target for the buildList. It will build the new version of master, which you can then just push up to
the server.
NEVER EVER EVER RUN "grunt build edge" AND THEN MERGE THE RESULTS WITH MASTER, because it will result in your stackato-config branch being merged into master.
This is bad because you want master to be build-target agnostic and be able to be deployed to DEV, QA or PROD. Merging a build into master will cause
master to contain the stackato config settings for that build. BAD.
*/
grunt.registerTask('build-new-master', 'Build New Master', function(){
grunt.config('shellopts',
{
branch: 'master',
list: grunt.buildList['2013-05'],
from: 'master'
}
);
grunt.task.run('build-branch');
});
};

AppCenter Grunt

ExactTarget, Platform Team
Christopher McCulloh (@cmcculloh)
2013

Grunt automation for AppCenter.

GitScripts

Allow grunt to utilize Git for build management This is kind of like a miniature, built-in version of GitScripts

GitScripts = (g) ->
	grunt = g;

	{

GitScripts.commands

A list of git commands available to grunt through the GitScripts object. You can use these in your GitScripts methods like: grunt.config("shell.checkoutMaster", {command: "checkout master", stdout: true}) which would run the mapped command git checkout master

		commands:
			"checkout master": 	"git checkout master"
			"checkout": 		"git checkout"
			"merge": 			"git merge"
			"hard reset": 		"git reset --hard"
			"reset": 			"git reset"
			"force delete": 	"git branch -D"
			"delete": 			"git branch -d"
			"create": 			"git checkout -b"

GitScripts.hello

This is a hello world type function to test out gitscripts and also use as an example of how to write a gitscripts funciton.

Notice that instead of overwriting the grunt config shell object, we extend it with our command.

If you are going to need to run the same command multiple times, doing something different each time, you would push the variables to an array that the function then looped over executing the function as many times as necessary. This sort of thing can be seen in the "merge" example below...

The way this works is that there is a "shell" task registered with grunt. The shell task is basically just an object with methods on it. Grunt will iterate through the object running each of the functions in turn. So, each function name must be unique, and, each method must be a top-level method on the object (meaning you can't nest methods). If you are expecting to run the same method multiple times, you will need to add a random string to its name (or something)'.

		hello: () ->
			grunt.config("shell.echo_test", {command: "echo test", stdout: true})

GitScripts.run

This actually kicks off the shell task, running all of the commands that have been stored on the shell object

		run: () ->
			grunt.task.run("shell")

Gitscripts.clear

Empties the shell object so that if called again, nothing happens (there will be no methods to iterate over)

		clear: () ->
			grunt.config("shell", {})



		checkoutMaster: () ->
			grunt.config("shell.checkout_master",{command: this.commands["checkout master"], stdout: true})
		
		checkout: (what) ->
			grunt.config('shell.checkout', {command: this.commands['checkout'] + " " + what, stdout: true})
		
		merge: (branchlist, flags) ->
			if not flags
				flags = ""

			for i in branchlist
				grunt.config("shell.merge" + i, {command: this.commands.merge + " " + flags + " origin/" + branchlist[i], stdout: true})
		
		reset: () ->
			grunt.config("shell.reset", {command: this.commands['hard reset'], stdout: true})
		
		del: (branch, safe) ->
			if safe is undefined
				safe = true

			if safe
				grunt.config("shell.delete", {command:this.commands["delete"] + " " + branch, stdout: true})
			else
				grunt.config("shell.delete", {command:this.commands["force delete"] + " " + branch, stdout: true})
		
		create: (branch, from) ->
			if from is undefined
				from = "master"

			grunt.config("shell.create", {command:this.commands["create"] + " " + branch + " " + from, stdout: true})
		
		deploy: () ->
			grunt.config("shell.deploy", {command:"stackato update --no-prompt", stdout:true})
		
	}

Grunt config

The actual Grunt configurations/tasks.

Define what you want returned in module.exports for require...

module.exports = (grunt) ->

Load the required npm tasks for grunt

	grunt.loadNpmTasks('grunt-contrib')
	grunt.loadNpmTasks('grunt-testem')
	grunt.loadNpmTasks('grunt-shell')

Project configuration

The actual grunt project configuration options

	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json')
		watch:
			files: ['grunt.js', 'public/javascripts/**/*.js']
			tasks: 'lint testem'
		jshint:
			uses_defaults: ['public/javascripts/**/*.js']
			options:
				curly: 		false
				eqeqeq: 	true
				immed: 		true
				latedef: 	true
				newcap: 	true
				noarg: 		true
				sub: 		true
				undef: 		true
				boss: 		true
				eqnull: 	true
				browser: 	true
				devel:		true
				laxcomma: 	true
				globals:
					define: 	true
					require: 	true
					$: 			true
					_: 			true
					io: 		true
					google: 	true
					tinymce: 	true
			with_overrides:
				options:
					browser: 	false
					node: 		true
				globals:
					__dirname: 	true
					module: 	true
					define: 	false
					require: 	false
					$: false
					_: false
				files:
					src: ['Gruntfile.js', 'app.js', 'lib/*.js']
		requirejs:
			combine:
				options:
					appDir: 					'public/'
					baseUrl: 					'vendor'
					dir: 						'public-optimized/'
					optimize: 					'uglify2'
					optimizeCss: 				'none'
					mainConfigFile: 			'public/javascripts/app/main.js'
					generateSourceMaps: 		true
					preserveLicenseComments:	false
					paths:
						config: 'empty:'
					modules: [
						{ name: 'app/main' }
					]
		shell: {}
	});

exec_file

Helper to execute a file and continually display its output

	grunt.exec_file = (file, args, options, done) ->
		cmd = require('child_process').execFile(file, args, options);
 
		cmd.stderr.on('data', (data) ->
			grunt.log.write(data.toString())
		)
 
		cmd.stdout.on('data', (data) ->
			grunt.log.write(data.toString())
		)
 
		cmd.on('exit', (error) ->
			done(!error)
		)

exec_cmd

Helper to execute a command and continually display its output

	grunt.exec_cmd =  (cmd, args, options, done) ->
		proc = require('child_process').exec(cmd, args, options)
 
		proc.stderr.on('data', (data) ->
			grunt.log.write(data.toString())
		)
 
		proc.stdout.on('data', (data) ->
			grunt.log.write(data.toString())
		)
 
		proc.on('exit', (error) ->
			done(!error)
		)

server task

Launch the project with an optional argument for Express environment

	serverTask = () ->
		done = this.async()
		args = ''
		useTrace = false
 
		process.env.HOST = ''
		process.env.NODE_ENV = ''
 
		for i in this.args

Detect named environment flags and set the NODE_ENV var based on them (maps directly to config/dev.js or config/qa1s1.js or config/edge.js etc)

			if this.args[i].match(/qa1|qa2|prod|dev|edge/)
				process.env.NODE_ENV = this.args[i]

Since there are multiple qa environments, determine if the user paseed the "qa" flag to indicate they are in a "qa like" environment and use the global settings from the qa.js config file. Any config grabbed based on the NODE_ENV var set about will override this

			if this.args[i].match(/^qa$/)
				process.env.HOST = this.args[i]

Determine if the user wants to use tracegl (look for the trace or tracegl argument

			if this.args[i] in ['tracegl', 'trace']
				useTrace = true
 
			args += "#{ this.args[i] } "

 
		if useTrace
			grunt.exec_cmd("node ~/tracegl #{ __dirname }/app.js -nolib -tgt:4000 #{ args }", this.args, {}, done)
		else

pass -q to silence debug messages... utilize forever to restart node server when it crashes

			grunt.exec_cmd('forever start ' + __dirname + '/app.js', this.args, {}, done);
			#grunt.exec_file( __dirname + '/' + 'app.js', this.args, {}, done);

Register the task with grunt

	grunt.registerTask('server', 'Runs the node.js app for this project', serverTask)

Default task

Stamp the project, run jshint to validate, then min/merge everything together

	grunt.registerTask('default', ['stamp', 'jshint', 'requirejs'])

stamp

append abbreviated githash to the top of version.txt so you can always see exactly which build is being served

	grunt.registerTask('stamp', 'Stamp', () -> 
		grunt.config("shell", {})
		grunt.config('shell.stamp', {command: 'git log -1 --abbrev=7 --format=oneline | cat - version.txt > /tmp/out && mv /tmp/out version.txt'})
		grunt.config('shell.time', {command: 'git log -1 --format="<br><b>%ci</b>:" | cat - version.txt > /tmp/out && mv /tmp/out version.txt'})
		#grunt.config('shell.newline', {command: 'echo "<br>\n" | cat - version.txt > /tmp/out && mv /tmp/out version.txt', stdout: true});
 
		grunt.task.run("shell")
	)
 
	grunt.registerTask('edge', 'Calls server with edge settings', () ->
		grunt.task.run('server:https:qa:edge-local')
	)

Debug task. Sets "devel" to true to not warn about console statements

	grunt.registerTask('development', 'Development Task', () ->
		grunt.config("jshint.options.devel", true)
		grunt.config("requirejs.combine.options.optimize", "none")
 
		grunt.task.run('default')
	)

build-branch

This will create a branch based on an arbitrary list of branches. This allows managing branches to be included in DEV, QA, Stage and Prod more easily.

Do not call this directly from command line. Instead, register a task that calls this after populating a "shellopts" grunt config:

grunt.registerTask('build-edge', 'Build edge', () -> grunt.config('shellopts', { branch: 'edge' #this indicates the branch that will be created once this task runs list: grunt.buildList['2013-06'].concat('edge-stackato-config') #specify the .net build you want to use for your buildList and then concat your stackato-config branch onto the end. You can specify multiple .net build by separating them with commas if you want... from: 'master' #starting point. You can specify any branch as a starting point. I almost always use master, but, this could be useful for a big project that wants to use another branch as its common ancestor } )

grunt.task.run('build-branch') )

	grunt.registerTask('build-branch', 'Builds a branch from an arbitrary list', () ->
		gitScripts = new GitScripts(grunt)
		gitScripts.clear()
		if grunt.config('shellopts.from') is undefined
			gitScripts.checkoutMaster();
		else
			gitScripts.checkout(grunt.config('shellopts.from'))
		
		gitScripts.del(grunt.config('shellopts.branch'), false)
		gitScripts.create(grunt.config('shellopts.branch'), grunt.config('shellopts.from'))
		gitScripts.merge(grunt.config('shellopts.list'), "--no-ff")
		gitScripts.run()
 
		grunt.task.run('default')
	)

Ideas: make it so this list can be managed from the browser, even just the stackato-deploy part

	grunt.buildList = 
		'2013-05': ['FTR---734---hide-nav-bar-on-page-load']
		'2013-06': ['FTR---734---hide-nav-bar-on-page-load'
				'BUG---793---edit-message-success-wrong']
		'2013-07': ['FTR---734---hide-nav-bar-on-page-load'
				'BUG---793---edit-message-success-wrong'
				'FTR---812---support-reload-at-any-url']
		'experimental': ['FTR---734---hide-nav-bar-on-page-load'
				'BUG---793---edit-message-success-wrong'
				'bootstrap-3'
				'nginx-support']

Each build target is defined in this way. To create a new build target, just copy the below and edit accordingly.

You will need to make a stackato-config branch for any new build targets. This branch should NEVER be merged back into master because prod has its own stackato configs that would be overwritten by this. Which is why you can just run "build-qa2s2" and then merge the results of that into master (you need to use the build-new-master task instead)

	grunt.registerTask('build-edge', 'Build edge', () ->
		grunt.config('shellopts',
			{
				branch: 'edge' #this indicates the branch that will be created once this task runs
				list: grunt.buildList['2013-06'].concat('edge-stackato-config') #specify the .net build you want to use for your buildList and then concat your stackato-config branch onto the end. You can specify multiple .net build by separating them with commas if you want...
				from: 'master' #starting point. You can specify any branch as a starting point. I almost always use master, but, this could be useful for a big project that wants to use another branch as its common ancestor
			}
		)
 
		grunt.task.run('build-branch')
	)

When a build target is promoted to prod, you will want to merge it in with master. Instead of checking out master and doing this manually, just run 'build-new-master' and use that build target for the buildList. It will build the new version of master, which you can then just push up to the server.

NEVER EVER EVER RUN "grunt build edge" AND THEN MERGE THE RESULTS WITH MASTER, because it will result in your stackato-config branch being merged into master. This is bad because you want master to be build-target agnostic and be able to be deployed to DEV, QA or PROD. Merging a build into master will cause master to contain the stackato config settings for that build. BAD.

	grunt.registerTask('build-new-master', 'Build New Master', () ->
		grunt.config('shellopts',
			{
				branch: 'master'
				list: grunt.buildList['2013-05']
				from: 'master'
			}
		)
 
		grunt.task.run('build-branch')
	)
@plumlee
Copy link

plumlee commented Aug 5, 2013

You can now put jshint options in package.json, which is nice as it exposes them to everything vs just grunt. http://www.jshint.com/blog/2013-08-02/npm/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment