Skip to content

Instantly share code, notes, and snippets.

@raymondmars
Last active September 8, 2022 08:19
Show Gist options
  • Save raymondmars/0aed27a95291892a670b to your computer and use it in GitHub Desktop.
Save raymondmars/0aed27a95291892a670b to your computer and use it in GitHub Desktop.
Node project deploy script
'use strict';
module.exports = function(grunt) {
// Please see the Grunt documentation for more information regarding task
// creation: http://gruntjs.com/creating-tasks
grunt.registerMultiTask('deploy', 'Your task description goes here.', function() {
var self = this;
var done = self.async();
var Connection = require('ssh2');
var moment = require('moment');
var timeStamp = moment().format('YYYYMMDDHHmmssSSS');
var options = self.options();
var connections = [];
var execSingleServer = function(server, connection){
var exec = function(cmd, showLog, next){
//console.log(server.username + "@" + server.host + ":~$ " + cmd);
connection.exec(cmd, function(err, stream) {
if (err) {throw err;}
stream.on('data', function(data, extended) {
showLog && console.log(data + '');
});
stream.on('end', function() {
next && next();
});
});
};
var execCmds = function(cmds, index, showLog, next){
if(!cmds || cmds.length <= index) {
next && next();
}
else{
exec(cmds[index++], showLog, function(){
execCmds(cmds,index,true,next);
})
}
}
console.log('executing cmds before deploy');
execCmds(options.cmds_before_deploy, 0, true, function(){
console.log('cmds before deploy executed');
//prepare folder
var prepareFolder = 'mkdir -p '+ options.deploy_path + '/node_modules && mkdir -p '+ options.deploy_path + '/releases ';// && mkdir -p '+ options.deploy_path + '/current '
var create_new_release_Folder = 'cd ' + options.deploy_path + '/releases && mkdir ' + timeStamp;
var removeCurrent = 'rm -rf ' + options.deploy_path + '/current';
var setCurrent = 'ln -s ' + options.deploy_path + '/releases/' + timeStamp + ' ' + options.deploy_path + '/current';
//set node modules soft link
var setModules = 'ln -s ' + options.deploy_path + '/node_modules ' + options.deploy_path + '/current/node_modules';
//install dependency modules /use taobao cnpm
var install_dependency = ' cd ' + options.deploy_path + '/current && cnpm install --production '
console.log('start deploy');
exec(prepareFolder + ' && ' + create_new_release_Folder + ' && ' + removeCurrent + ' && ' + setCurrent + ' && ' + setModules, false,function(){
var sys = require('sys')
var execLocal = require('child_process').exec;
var child;
//use rsync
child = execLocal("cp -r ./lib ./dist/ && cp ./package.json ./dist/ && cp ./app.coffee ./dist/ && rsync -r -e 'ssh -p " + server.port + "' --exclude='.*' ./dist/ " + server.username + "@" + server.host + ":" + options.deploy_path + "/releases/" + timeStamp, function (error, stdout, stderr) {
console.log('end deploy');
console.log('install dependency...');
exec(install_dependency,false,function() {
console.log('executing cmds after deploy');
execCmds(options.cmds_after_deploy, 0, true, function(e,r,s){
console.log('cmds after deploy executed');
connection.end();
});
});
});
})
})
}
var length = options.servers.length;
var completed = 0;
var checkCompleted = function(){
completed++;
if(completed>=length){
done();
}
}
options.servers.forEach(function(server){
var c = new Connection();
c.on('connect', function() {
console.log('Connecting to server: ' + server.host);
});
c.on('ready', function() {
console.log('Connected to server: ' + server.host);
execSingleServer(server,c);
});
c.on('error', function(err) {
console.log("Error on server: " + server.host)
console.error(err);
if (err) {throw err;}
});
c.on('close', function(had_error) {
console.log("Closed connection for server: " + server.host);
checkCompleted();
});
c.connect(server);
})
});
};
'use strict';
module.exports = function (grunt) {
// show elapsed time at the end
require('time-grunt')(grunt);
// load all grunt tasks
require('load-grunt-tasks')(grunt);
var path_ex = require('path-extra');
grunt.initConfig({
// Watch Config
watch: {
files: ['views/**/*'],
options: {
livereload: true
},
scripts: {
files: [
'assets/scripts/**/*.js',
],
},
css: {
files: [
'assets/styles/**/*.css',
],
},
sass: {
files: ['assets/styles/**/*.sass'],
tasks: ['sass:dev']
},
images: {
files: [
'assets/images/**/*.{png,jpg,jpeg,webp}'
],
},
express: {
files: [ 'app.coffee', '!**/node_modules/**', '!Gruntfile.js' ],
tasks: [ 'express:dev' ],
options: {
nospawn: true // Without this option specified express won't be reloaded
}
},
},
// Clean Config
clean: {
dist: {
files: [{
dot: true,
src: [
'.tmp',
'dist/*',
'!dist/.git*'
]
}]
},
server: ['.tmp'],
},
// Hint Config
jshint: {
options: {
jshintrc: '.jshintrc'
},
all: [
'Gruntfile.js',
'assets/scripts/**/*.js',
'!assets/scripts/vendor/*',
'test/spec/**/*.js'
]
},
// Sass Config
sass: {
options: {
cacheLocation: '.tmp/.sass-cache'
},
dev: {
options: {
style: 'expanded',
lineComments: true
},
files: [{
expand: true,
cwd: 'assets/styles/sass',
dest: 'assets/styles',
src: ['screen.sass'],
ext: '.css'
}]
}
},
// Express Config
express: {
options: {
// Override defaults here
/*cmd: 'coffee'*/
cmd: process.argv[0],
opts: ['node_modules/coffee-script/bin/coffee']
},
dev: {
options: {
script: 'app.coffee'
}
}
},
// Open Config
open: {
site: {
path: 'http://localhost:9001',
app: 'Google Chrome'
},
editor: {
path: './',
},
},
// Rev Config
rev: {
dist: {
files: {
src: [
'dist/assets/scripts/**/*.js',
'dist/assets/styles/**/*.css',
'dist/assets/images/**/*.{png,jpg,jpeg,gif,webp}',
'dist/assets/styles/fonts/**/*.*'
]
}
}
},
// Usemin Config
useminPrepare: {
options: {
dest: 'dist/assets'
},
html: ['assets/{,*/}*.html', 'views/**/*.handlebars']
},
usemin: {
options: {
dirs: ['dist/assets'],
basedir: 'dist/assets',
},
html: ['dist/assets/{,*/}*.html', 'dist/views/**/*.handlebars'],
css: ['dist/assets/styles/{,*/}*.css']
},
// Imagemin Config
imagemin: {
dist: {
files: [{
expand: true,
cwd: 'assets/images',
src: '**/*.{png,jpg,jpeg}',
dest: 'dist/assets/images'
}]
}
},
// SVGmin Config
svgmin: {
dist: {
files: [{
expand: true,
cwd: 'assets/images',
src: '{,*/}*.svg',
dest: 'dist/assets/images'
}]
}
},
// HTML Config
htmlmin: {
dist: {
options: {
/*removeCommentsFromCDATA: true,
// https://github.com/yeoman/grunt-usemin/issues/44
//collapseWhitespace: true,
collapseBooleanAttributes: true,
removeAttributeQuotes: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeOptionalTags: true*/
},
files: [{
expand: true,
cwd: 'assets',
src: '*.html',
dest: 'dist/assets'
}]
}
},
// Copy Config
// Put files not handled in other tasks here
copy: {
dist: {
files: [{
expand: true,
dot: true,
cwd: 'assets',
dest: 'dist/assets',
src: [
'*.{ico,png,txt}',
'.htaccess',
'images/**/*.{webp,gif}',
'styles/fonts/{,*/}*.*',
'fonts/**/*.*',
]
}, {
expand: true,
dot: true,
cwd: 'views',
dest: 'dist/views/',
src: '**/*.handlebars',
}]
},
styles: {
expand: true,
dot: true,
cwd: 'assets/styles',
dest: '.tmp/styles/',
src: '{,*/}*.css'
},
client_notneedprocess_file: {
expand: true,
dot: true,
cwd: 'assets',
dest: 'dist/assets',
src: ['images/shake/s1.png','images/shake/s2.png']
},
},
// Concurrent Config
concurrent: {
dist: [
'copy:styles',
'svgmin',
'htmlmin'
]
},
deploy: {
production: {
options: {
servers: [{
host: "Your server ip address",
port: portnumber,
username: "Your username",
privateKey: require('fs').readFileSync(path_ex.join(path_ex.homedir(),'.ssh/id_rsa'), 'utf8')
}],
cmds_before_deploy: [],
cmds_after_deploy: ["cd /data/www/nodeapp/current/ && NODE_ENV=production pm2 kill && NODE_ENV=production pm2 start app.coffee"],
/*cmds_after_deploy: [],*/
deploy_path: "/data/www/nodeapp"
}
}
}
});
grunt.loadTasks('tasks');
// Register Tasks
// Workon
grunt.registerTask('workon', 'Start working on this project.', [
//'jshint',
'sass:dev',
'express:dev',
/*'open:site',*/
//'open:editor',
'watch'
]);
// Restart
grunt.registerTask('restart', 'Restart the server.', [
'express:dev',
'watch'
]);
// Build
grunt.registerTask('build', 'Build production ready assets and views.', [
'clean:dist',
'concurrent:dist',
'useminPrepare',
'imagemin',
'concat',
'cssmin',
'uglify',
'copy:dist',
'rev',
'usemin',
'copy:client_notneedprocess_file'
]);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment