Skip to content

Instantly share code, notes, and snippets.

@adamrneary
Last active March 22, 2016 17:23
Show Gist options
  • Save adamrneary/a003e351b7fdf832b3ad to your computer and use it in GitHub Desktop.
Save adamrneary/a003e351b7fdf832b3ad to your computer and use it in GitHub Desktop.
const _ = require('lodash');
const argv = require('minimist')(process.argv.slice(2));
const awspublish = require('gulp-awspublish');
const dotenv = require('dotenv');
const del = require('del');
const GitHubApi = require('github');
const git = require('git-rev')
const gulp = require('gulp');
const gutil = require('gulp-util');
const path = require('path');
const release = require('gulp-github-release');
const rename = require('gulp-rename');
const replace = require('gulp-replace');
const rev = require('gulp-rev');
const runSequence = require('run-sequence').use(gulp);
const Slack = require('slack-notify');
const usemin = require('gulp-usemin');
const webpack = require('webpack');
const webpackConf = require('webpack-config');
// These deploy environments correspond to suffixes in ./src/config/
const DEPLOY_ENVS = ['smash', 'staging', 'preprod', 'demo', 'production', 'sera'];
// Load appropriate environment variables.
if (DEPLOY_ENVS.indexOf(argv.env) === -1) {
gutil.log('[env]', 'No/invalid deploy env specified.');
gutil.log('[env]', 'Valid deploy environments are ' + DEPLOY_ENVS.join(', '));
gutil.log('[env]', 'Syntax example: gulp deploy --env=production');
throw new Error('No/invalid deploy env specified. Exiting.');
} else {
gutil.log('[env]', 'Loading ' + argv.env + ' config.');
// We need to hard-set this because remote envs like test will have the
// NODE_ENV already set.
process.env.NODE_ENV = argv.env;
dotenv.config({path: '../config/.env.' + argv.env});
dotenv.config({path: './config/.env.' + argv.env});
}
const BUILD_DIR = path.resolve('../../build/Admin');
const DIST_DIR = path.resolve('../../dist/Admin');
const github = new GitHubApi({
// required
version: '3.0.0',
protocol: 'https',
host: 'api.github.com',
timeout: 5000,
headers: {
'user-agent': 'Parsable Deploy Agent'
}
});
github.authenticate({
type: 'oauth',
token: process.env.GITHUB_TOKEN
});
gulp.task('set-build-number', function(cb) {
github.releases.listReleases({
owner: 'parsable',
repo: 'gutenberg',
/*eslint-disable */
per_page: 1
/*eslint-enable */
}, function(err, result) {
if (err) {throw new Error(err); }
const existingBuildNo = parseInt(result[0].tag_name.replace('build-', ''));
if (_.isNaN(existingBuildNo)) {
throw new Error('Existing release is not numbered as expected.');
}
process.env.BUILD_NUMBER = Math.round(existingBuildNo) + 1;
cb();
});
});
gulp.task('clean', function(cb) {
del([BUILD_DIR, DIST_DIR], cb);
});
gulp.task('pack', function(cb) {
const conf = new webpackConf().extend('./webpack.config.js');
const serverConf = conf.devServer;
const webpackCompiler = webpack(conf);
webpackCompiler.run(function(err, stats) {
if (err) { throw err; }
gutil.log('[webpack]', stats.toString(serverConf.stats));
cb();
});
});
// This replace pipeline saves us from having to process html in dev mode.
// Well worth the trickery, though more elegant alternatives are welcome!
// Ultimately, webpack can handle adding hashes to assets, and it can manage
// this part of the pipeline. But for now, this works fine.
gulp.task('pre-process-html', function() {
const inPlaceComment = '<!-- @built-css -->';
const cssBlocks =
'<!-- build:css /css/app.css -->\n' +
' <link rel="stylesheet" href="app.css">\n' +
'<!-- endbuild -->\n';
const vendorJsBlock =
'<!-- build:js1 /js/vendor.js -->\n' +
' <script src="vendor.js"></script>\n' +
'<!-- endbuild -->\n';
const appJsBlock =
'<!-- build:js2 /js/app.js -->\n' +
' <script src="app.js"></script>\n' +
'<!-- endbuild -->\n';
var buildTag = '<script>window.wiBuildNumber=' + (process.env.BUILD_NUMBER || 0) + ';</script>';
var inPlaceBuildTag = '<!-- @build-tag -->';
// Omitting some code from this gist…
return gulp.src('./index.html')
// .pipe(replace(inPlaceCSP, cspBlock))
.pipe(replace(inPlaceBuildTag, buildTag))
.pipe(replace(inPlaceComment, cssBlocks))
.pipe(replace(inPlaceNewRelic, newRelicBlock))
.pipe(replace(inPlaceSegment, segmentBlock))
.pipe(replace(inPlaceLoggly, logglyBlock))
.pipe(replace('<script src="/vendor.js"></script>', vendorJsBlock))
.pipe(replace('<script src="/app.js"></script>', appJsBlock))
.pipe(gulp.dest(BUILD_DIR));
});
gulp.task('process-html', function() {
return gulp.src(BUILD_DIR + '/index.html')
.pipe(usemin({
css: [rev()],
js1: [uglify(), rev()],
js2: [uglify(), rev()],
html: [minifyHtml({empty: true})],
}))
.pipe(gulp.dest(DIST_DIR))
.pipe(rename('index' + process.env.BUILD_NUMBER + '.html'))
.pipe(gulp.dest(DIST_DIR));
});
gulp.task('copy-images', function() {
return gulp.src(BUILD_DIR + '/**/!(*.html|*.js|*.css)')
.pipe(gulp.dest(DIST_DIR));
});
gulp.task('tag', function(){
git.long(function (gitRev) {
console.log('Using git revision: ', gitRev, ' for release tag.');
gulp.src(DIST_DIR + '/index.html')
.pipe(release({
repo: 'gutenberg',
owner: 'parsable',
/*eslint-disable */
target_commitish: gitRev,
/*eslint-enable */
tag: 'build-' + process.env.BUILD_NUMBER,
name: 'Build ' + process.env.BUILD_NUMBER + ' released to ' + process.env.DEPLOY_ENV,
notes: 'Tagged via gulp deploy',
draft: false,
prerelease: false,
manifest: require('../../package.json')
}));
})
});
var publisher;
publisher = awspublish.create({
params: {Bucket: process.env.AWS_S3_BUCKET},
accessKeyId: process.env[`aws_deploy_${argv.env}_id`],
secretAccessKey: process.env[`aws_deploy_${argv.env}_key`],
region: process.env.AWS_S3_REGION
});
gulp.task('upload:bare', function() {
gulp.src(DIST_DIR + '/**/*.html')
.pipe(awspublish.gzip())
.pipe(publisher.publish({'Cache-Control': 'max-age=1'}))
.pipe(awspublish.reporter());
});
gulp.task('upload:revved', function() {
gulp.src([DIST_DIR + '/**/*', '!' + DIST_DIR + '/**/*.html'])
.pipe(awspublish.gzip())
.pipe(publisher.publish({'Cache-Control': 'max-age=315360000'}))
.pipe(awspublish.reporter());
});
gulp.task('brag', function() {
if (
process.env.SLACK_TOKEN
) {
const slack = Slack('https://hooks.slack.com/services/T0255PDBV/B07KSHJ3F/' +
process.env.SLACK_TOKEN
);
const message = 'Web Admin build ' + process.env.BUILD_NUMBER + ' deployed to ' + process.env.DEPLOY_ENV;
slack.success({
username: 'Parsable Continuous Integration (via Gulp and Circle)',
channel: '#deploys',
/*eslint-disable */
icon_emoji: ':doitright:',
/*eslint-enable */
text: message
}, function(err, result) {
if (err) {throw new Error(err); }
console.log(result);
});
} else {
throw new Error('Cannot brag. Slack token unavailable.');
}
});
// TODO: It sounds like runSequence is deprecated and we should migrate to
// orchestrator. Whatever. Build tools gonna build tools.
gulp.task('build', function() {
return runSequence(
['clean'],
['pack'],
['pre-process-html'],
['process-html', 'copy-images']
);
});
// TODO: It sounds like runSequence is deprecated and we should migrate to
// orchestrator. Whatever. Build tools gonna build tools.
gulp.task('default', function() {
return runSequence(
['set-build-number'],
['clean'],
['pack'],
['pre-process-html'],
['process-html', 'copy-images'],
['tag'],
['upload:bare', 'upload:revved'],
['brag']
);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment