530 lines
16 KiB
JavaScript
530 lines
16 KiB
JavaScript
|
var _ = require('underscore');
|
||
|
var timer = require('grunt-timer');
|
||
|
var semver = require('semver');
|
||
|
var jasmineCfg = require('./lib/build/tasks/jasmine.js');
|
||
|
var execSync = require('child_process').execSync;
|
||
|
var lockedDependencies = require('./lib/build/tasks/locked-dependencies.js');
|
||
|
var webpackTask = null;
|
||
|
var EDITOR_ASSETS_VERSION = require('./config/editor_assets_version.json').version;
|
||
|
|
||
|
var REQUIRED_NODE_VERSIONS = ['10.x', '6.9.2'];
|
||
|
var REQUIRED_NPM_VERSIONS = ['6.x', '3.10.9'];
|
||
|
|
||
|
var DEVELOPMENT = 'development';
|
||
|
|
||
|
var LOCKED_MODULES_TO_VALIDATE = [
|
||
|
'backbone',
|
||
|
'camshaft-reference',
|
||
|
'carto',
|
||
|
'internal-carto.js',
|
||
|
'cartocolor',
|
||
|
'd3',
|
||
|
'jquery',
|
||
|
'leaflet',
|
||
|
'perfect-scrollbar',
|
||
|
'torque.js',
|
||
|
'turbo-carto',
|
||
|
];
|
||
|
|
||
|
// Synchronously check if editor assets have changed
|
||
|
var diff = execSync('git diff --numstat $(git rev-list --tags --skip=1 --max-count=1) -- $(git symbolic-ref --short HEAD) config/editor_assets_version.json', {
|
||
|
cwd: __dirname
|
||
|
});
|
||
|
var EDITOR_ASSETS_CHANGED = diff.toString().length > 0;
|
||
|
|
||
|
function requireWebpackTask () {
|
||
|
if (webpackTask === null) {
|
||
|
webpackTask = require('./lib/build/tasks/webpack/webpack.js');
|
||
|
}
|
||
|
return webpackTask;
|
||
|
}
|
||
|
|
||
|
function logVersionsError (err, requiredNodeVersions, requiredNpmVersions) {
|
||
|
if (err) {
|
||
|
grunt.log.fail('############### /!\\ CAUTION /!\\ #################');
|
||
|
grunt.log.fail('PLEASE installed required versions to build CARTO:\n- node: ' + requiredNodeVersions.join(', ') + '\n- npm: ' + requiredNpmVersions.join(', '));
|
||
|
grunt.log.fail('#################################################');
|
||
|
process.exit(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function getTargetDiff () {
|
||
|
var target = require('child_process').execSync('(git diff --name-only --relative || true;' +
|
||
|
'git diff origin/master.. --name-only --relative || true;)' +
|
||
|
'| grep \'\\.js\\?$\' || true').toString();
|
||
|
if (target.length === 0) {
|
||
|
target = ['.'];
|
||
|
} else {
|
||
|
target = target.split('\n');
|
||
|
target.splice(-1, 1);
|
||
|
}
|
||
|
return target;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* CartoDB UI assets generation
|
||
|
*/
|
||
|
module.exports = function (grunt) {
|
||
|
if (timer) timer.init(grunt);
|
||
|
|
||
|
var environment = grunt.option('environment') || DEVELOPMENT;
|
||
|
grunt.log.writeln('Environment: ' + environment);
|
||
|
|
||
|
var runningTasks = grunt.cli.tasks;
|
||
|
if (runningTasks.length === 0) {
|
||
|
grunt.log.writeln('Running default task.');
|
||
|
} else {
|
||
|
grunt.log.writeln('Running tasks: ' + runningTasks);
|
||
|
}
|
||
|
|
||
|
function preFlight (requiredNodeVersions, requiredNpmVersions, logFn) {
|
||
|
function checkVersion (cmd, versionRange, name, logFn) {
|
||
|
grunt.log.writeln('Required ' + name + ' version: ' + versionRange.join(', '));
|
||
|
require('child_process').exec(cmd, function (error, stdout, stderr) {
|
||
|
var err = null;
|
||
|
if (error) {
|
||
|
err = 'failed to check version for ' + name;
|
||
|
} else {
|
||
|
var installed = semver.clean(stdout);
|
||
|
if (!semver.satisfies(installed, versionRange.join(' || '))) {
|
||
|
err = 'Installed ' + name + ' version does not match with required [' + versionRange.join(', ') + '] Installed: ' + installed;
|
||
|
}
|
||
|
}
|
||
|
if (err) {
|
||
|
grunt.log.fail(err);
|
||
|
}
|
||
|
logFn && logFn(err ? new Error(err) : null);
|
||
|
});
|
||
|
}
|
||
|
checkVersion('node -v', requiredNodeVersions, 'node', logFn);
|
||
|
checkVersion('npm -v', requiredNpmVersions, 'npm', logFn);
|
||
|
}
|
||
|
|
||
|
var mustCheckNodeVersion = grunt.option('no-node-checker');
|
||
|
if (!mustCheckNodeVersion) {
|
||
|
preFlight(REQUIRED_NODE_VERSIONS, REQUIRED_NPM_VERSIONS, logVersionsError);
|
||
|
grunt.log.writeln('');
|
||
|
}
|
||
|
|
||
|
var duplicatedModules = lockedDependencies.checkDuplicatedDependencies(require('./package-lock.json'), LOCKED_MODULES_TO_VALIDATE);
|
||
|
if (duplicatedModules.length > 0) {
|
||
|
grunt.log.fail('############### /!\\ CAUTION /!\\ #################');
|
||
|
grunt.log.fail('Duplicated dependencies found in package-lock.json file.');
|
||
|
grunt.log.fail(JSON.stringify(duplicatedModules, null, 4));
|
||
|
grunt.log.fail('#################################################');
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
var PUBLIC_DIR = './public/';
|
||
|
var ROOT_ASSETS_DIR = './public/assets/';
|
||
|
var ASSETS_DIR = './public/assets/<%= pkg.version %>';
|
||
|
var EDITOR_ASSETS_DIR = `./public/assets/editor/${EDITOR_ASSETS_VERSION}`;
|
||
|
|
||
|
/**
|
||
|
* this is being used by `grunt --environment=production release`
|
||
|
*/
|
||
|
var env = './config/grunt_' + environment + '.json';
|
||
|
grunt.log.writeln('env: ' + env);
|
||
|
|
||
|
if (grunt.file.exists(env)) {
|
||
|
env = grunt.file.readJSON(env);
|
||
|
} else {
|
||
|
throw grunt.util.error(env + ' file is missing! See ' + env + '.sample for how it should look like');
|
||
|
}
|
||
|
|
||
|
var aws = {};
|
||
|
if (grunt.file.exists('./lib/build/grunt-aws.json')) {
|
||
|
aws = grunt.file.readJSON('./lib/build/grunt-aws.json');
|
||
|
}
|
||
|
|
||
|
var targetDiff = getTargetDiff();
|
||
|
|
||
|
// Project configuration.
|
||
|
grunt.initConfig({
|
||
|
pkg: grunt.file.readJSON('package.json'),
|
||
|
aws: aws,
|
||
|
env: env,
|
||
|
|
||
|
public_dir: PUBLIC_DIR,
|
||
|
assets_dir: ASSETS_DIR,
|
||
|
editor_assets_dir: EDITOR_ASSETS_DIR,
|
||
|
editor_assets_version: EDITOR_ASSETS_VERSION,
|
||
|
root_assets_dir: ROOT_ASSETS_DIR,
|
||
|
|
||
|
// Concat task
|
||
|
concat: require('./lib/build/tasks/concat').task(),
|
||
|
|
||
|
// JST generation task
|
||
|
jst: require('./lib/build/tasks/jst').task(),
|
||
|
|
||
|
// Compass files generation
|
||
|
compass: require('./lib/build/tasks/compass').task(),
|
||
|
|
||
|
// Copy assets (stylesheets, javascripts, images...)
|
||
|
copy: require('./lib/build/tasks/copy').task(grunt),
|
||
|
|
||
|
// Watch actions
|
||
|
watch: require('./lib/build/tasks/watch.js').task(),
|
||
|
|
||
|
// Clean folders before other tasks
|
||
|
clean: require('./lib/build/tasks/clean').task(),
|
||
|
|
||
|
jasmine: jasmineCfg,
|
||
|
|
||
|
// Create a tarball of the static pages for production release
|
||
|
compress: require('./lib/build/tasks/compress.js').task(),
|
||
|
|
||
|
s3: require('./lib/build/tasks/s3.js').task(),
|
||
|
|
||
|
exorcise: require('./lib/build/tasks/exorcise.js').task(),
|
||
|
|
||
|
uglify: require('./lib/build/tasks/uglify.js').task(),
|
||
|
|
||
|
browserify: require('./lib/build/tasks/browserify.js').task(),
|
||
|
|
||
|
connect: require('./lib/build/tasks/connect.js').task(),
|
||
|
|
||
|
availabletasks: require('./lib/build/tasks/availabletasks.js').task(),
|
||
|
|
||
|
sass: require('./lib/build/tasks/sass.js').task(),
|
||
|
|
||
|
eslint: {
|
||
|
target: targetDiff
|
||
|
}
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* `grunt availabletasks`
|
||
|
*/
|
||
|
grunt.loadNpmTasks('grunt-available-tasks');
|
||
|
|
||
|
// Load Grunt tasks
|
||
|
require('load-grunt-tasks')(grunt, {
|
||
|
pattern: ['grunt-*', '@*/grunt-*', '!grunt-timer']
|
||
|
});
|
||
|
|
||
|
require('./lib/build/tasks/manifest').register(grunt, ASSETS_DIR);
|
||
|
|
||
|
grunt.registerTask('invalidate', 'invalidate cache', function () {
|
||
|
var done = this.async();
|
||
|
var url = require('url');
|
||
|
var https = require('https');
|
||
|
|
||
|
var options = url.parse(grunt.template.process('https://api.fastly.com/service/<%= aws.FASTLY_CARTODB_SERVICE %>/purge_all'));
|
||
|
options['method'] = 'POST';
|
||
|
options['headers'] = {
|
||
|
'Fastly-Key': aws.FASTLY_API_KEY,
|
||
|
'Content-Length': '0' // Disables chunked encoding
|
||
|
};
|
||
|
console.log(options);
|
||
|
|
||
|
https.request(options, function (response) {
|
||
|
if (response.statusCode === 200) {
|
||
|
grunt.log.ok('CDN invalidated (fastly)');
|
||
|
} else {
|
||
|
grunt.log.error('CDN not invalidated (fastly), code: ' + response.statusCode);
|
||
|
}
|
||
|
done();
|
||
|
}).on('error', function () {
|
||
|
grunt.log.error('CDN not invalidated (fastly)');
|
||
|
done();
|
||
|
}).end();
|
||
|
});
|
||
|
|
||
|
grunt.registerTask('config', 'generates assets config for current configuration', function () {
|
||
|
// Set assets url for static assets in our app
|
||
|
var config = grunt.template.process('cdb.config.set(\'assets_url\', \'<%= env.http_path_prefix %>/assets/<%= pkg.version %>\');');
|
||
|
config += grunt.template.process('\nconsole.log(\'cartodbui v<%= pkg.version %>\');');
|
||
|
grunt.file.write('lib/build/app_config.js', config);
|
||
|
});
|
||
|
|
||
|
grunt.registerTask('check_release', 'checks release can be done', function () {
|
||
|
if (environment === DEVELOPMENT) {
|
||
|
grunt.log.error('you can\'t release running development environment');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
grunt.log.ok('************************************************');
|
||
|
grunt.log.ok(' you are going to deploy to ' + env);
|
||
|
grunt.log.ok('************************************************');
|
||
|
});
|
||
|
|
||
|
grunt.event.on('watch', function (action, filepath, subtask) {
|
||
|
// Configure copy vendor to only run on changed file
|
||
|
var vendorFile = 'copy.vendor';
|
||
|
var vendorFileCfg = grunt.config.get(vendorFile);
|
||
|
|
||
|
if (filepath.indexOf(vendorFileCfg.cwd) !== -1) {
|
||
|
grunt.config(vendorFile + '.src', filepath.replace(vendorFileCfg.cwd, ''));
|
||
|
} else {
|
||
|
grunt.config(vendorFile + 'src', []);
|
||
|
}
|
||
|
|
||
|
// Configure copy app to only run on changed files
|
||
|
var files = 'copy.app.files';
|
||
|
var filesCfg = grunt.config.get(files);
|
||
|
|
||
|
for (var i = 0, l = filesCfg.length; i < l; ++i) {
|
||
|
var file = files + '.' + i;
|
||
|
var fileCfg = grunt.config.get(file);
|
||
|
|
||
|
if (filepath.indexOf(fileCfg.cwd) !== -1) {
|
||
|
grunt.config(file + '.src', filepath.replace(fileCfg.cwd, ''));
|
||
|
} else {
|
||
|
grunt.config(file + '.src', []);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
grunt.registerTask('css', [
|
||
|
'copy:vendor',
|
||
|
'copy:app',
|
||
|
'copy:css_cartodb',
|
||
|
'compass',
|
||
|
'sass',
|
||
|
'concat:css'
|
||
|
]);
|
||
|
|
||
|
grunt.registerTask('run_browserify', 'Browserify task with options', function (option) {
|
||
|
var skipAllSpecs = false;
|
||
|
|
||
|
if (environment !== DEVELOPMENT) {
|
||
|
grunt.log.writeln('Skipping all specs generation by browserify because not in development environment.');
|
||
|
skipAllSpecs = true;
|
||
|
}
|
||
|
|
||
|
if (skipAllSpecs) {
|
||
|
delete grunt.config.data.browserify['test_specs_for_browserify_modules'];
|
||
|
}
|
||
|
|
||
|
grunt.task.run('browserify');
|
||
|
});
|
||
|
|
||
|
grunt.registerTask('cdb', 'build cartodb.js', function () {
|
||
|
var done = this.async();
|
||
|
|
||
|
require('child_process').exec('make update_cdb', function (error, stdout, stderr) {
|
||
|
if (error) {
|
||
|
grunt.log.fail('cartodb.js not updated (due to ' + stdout + ', ' + stderr + ')');
|
||
|
} else {
|
||
|
grunt.log.ok('cartodb.js updated');
|
||
|
}
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
grunt.registerTask('js_editor', [
|
||
|
'cdb',
|
||
|
'setConfig:env.browserify_watch:true',
|
||
|
'npm-carto-node',
|
||
|
'run_browserify',
|
||
|
'concat:js',
|
||
|
'jst'
|
||
|
]);
|
||
|
|
||
|
grunt.registerTask('beforeDefault', [
|
||
|
'config'
|
||
|
]);
|
||
|
|
||
|
grunt.registerTask('dev-editor', [
|
||
|
'beforeDefault',
|
||
|
'js_editor',
|
||
|
'css',
|
||
|
'manifest'
|
||
|
]);
|
||
|
|
||
|
registerCmdTask('npm-start', {cmd: 'npm', args: ['run', 'start']});
|
||
|
registerCmdTask('npm-build', {cmd: 'npm', args: ['run', 'build']});
|
||
|
registerCmdTask('npm-build-static', {cmd: 'npm', args: ['run', 'build:static']});
|
||
|
registerCmdTask('npm-carto-node', {cmd: 'npm', args: ['run', 'carto-node']});
|
||
|
registerCmdTask('npm-build-dev', {cmd: 'npm', args: ['run', 'build:dev']});
|
||
|
|
||
|
/**
|
||
|
* `grunt dev`
|
||
|
*/
|
||
|
|
||
|
grunt.registerTask('editor', [
|
||
|
'build-static',
|
||
|
'npm-build-dev',
|
||
|
'dev-editor',
|
||
|
'watch:css'
|
||
|
]);
|
||
|
|
||
|
grunt.registerTask('default', [
|
||
|
'build-static',
|
||
|
'npm-build-dev',
|
||
|
'dev-editor'
|
||
|
]);
|
||
|
|
||
|
grunt.registerTask('lint', [
|
||
|
'eslint'
|
||
|
]);
|
||
|
|
||
|
grunt.registerTask('sourcemaps', 'generate sourcemaps, to be used w/ trackjs.com for bughunting', [
|
||
|
'setConfig:assets_dir:./tmp/sourcemaps',
|
||
|
'config',
|
||
|
'js',
|
||
|
'copy:js',
|
||
|
'exorcise',
|
||
|
'uglify'
|
||
|
]);
|
||
|
|
||
|
// -- BUILD TASKS
|
||
|
|
||
|
grunt.registerTask('build', 'build editor, builder, dashboard and static pages', [
|
||
|
'build-editor',
|
||
|
'build-static',
|
||
|
'npm-build'
|
||
|
]);
|
||
|
|
||
|
grunt.registerTask('build-editor', 'generate editor css and javasript files', [
|
||
|
'dev-editor',
|
||
|
'copy:js',
|
||
|
'exorcise',
|
||
|
'uglify'
|
||
|
]);
|
||
|
|
||
|
grunt.registerTask('build-static', 'generate static files and needed vendor scripts', [
|
||
|
'npm-carto-node',
|
||
|
'npm-build-static'
|
||
|
]);
|
||
|
|
||
|
/**
|
||
|
* `grunt release`
|
||
|
* `grunt release --environment=production`
|
||
|
*/
|
||
|
grunt.registerTask('release', [
|
||
|
'check_release',
|
||
|
'build-static',
|
||
|
'npm-build',
|
||
|
'compress',
|
||
|
's3:js',
|
||
|
's3:css',
|
||
|
's3:images',
|
||
|
's3:fonts',
|
||
|
's3:flash',
|
||
|
's3:favicons',
|
||
|
's3:unversioned',
|
||
|
's3:unversioned_onboarding',
|
||
|
's3:static_pages',
|
||
|
'invalidate'
|
||
|
]);
|
||
|
|
||
|
grunt.registerTask('release_editor_assets', 'builds & uploads editor assets', [
|
||
|
'build-editor',
|
||
|
's3:frozen',
|
||
|
'invalidate'
|
||
|
]);
|
||
|
|
||
|
grunt.registerTask('generate_builder_specs', 'Generate only builder specs', function (option) {
|
||
|
requireWebpackTask().affected.call(this, option, grunt);
|
||
|
});
|
||
|
|
||
|
grunt.registerTask('generate_dashboard_specs', 'Generate only dashboard specs', function (option) {
|
||
|
requireWebpackTask().dashboard.call(this, option, grunt);
|
||
|
});
|
||
|
|
||
|
grunt.registerTask('bootstrap_webpack_builder_specs', 'Create the webpack compiler', function () {
|
||
|
requireWebpackTask().bootstrap.call(this, 'builder_specs', grunt);
|
||
|
});
|
||
|
|
||
|
grunt.registerTask('bootstrap_webpack_dashboard_specs', 'Create the webpack compiler', function () {
|
||
|
requireWebpackTask().bootstrap.call(this, 'dashboard_specs', grunt);
|
||
|
});
|
||
|
|
||
|
grunt.registerTask('webpack:builder_specs', 'Webpack compilation task for builder specs', function () {
|
||
|
requireWebpackTask().compile.call(this, 'builder_specs');
|
||
|
});
|
||
|
|
||
|
grunt.registerTask('webpack:dashboard_specs', 'Webpack compilation task for dashboard specs', function () {
|
||
|
requireWebpackTask().compile.call(this, 'dashboard_specs');
|
||
|
});
|
||
|
|
||
|
var testTasks = [
|
||
|
'connect:test',
|
||
|
'beforeDefault',
|
||
|
'generate_builder_specs',
|
||
|
'bootstrap_webpack_builder_specs',
|
||
|
'webpack:builder_specs',
|
||
|
'jasmine:builder',
|
||
|
'generate_dashboard_specs',
|
||
|
'bootstrap_webpack_dashboard_specs',
|
||
|
'webpack:dashboard_specs',
|
||
|
'jasmine:dashboard',
|
||
|
'lint'
|
||
|
];
|
||
|
|
||
|
// If the editor assets version has changed, add the editor tests
|
||
|
if (EDITOR_ASSETS_CHANGED) {
|
||
|
testTasks.splice(testTasks.indexOf('generate_builder_specs'), 0, 'js_editor', 'jasmine:cartodbui');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* `grunt test`
|
||
|
*/
|
||
|
grunt.registerTask('test', '(CI env) Re-build JS files and run all tests. For manual testing use `grunt jasmine` directly', testTasks);
|
||
|
|
||
|
/**
|
||
|
* `grunt test:browser` compile all Builder specs and launch a webpage in the browser.
|
||
|
*/
|
||
|
grunt.registerTask('test:browser:builder', 'Build all Builder specs', [
|
||
|
'generate_builder_specs',
|
||
|
'bootstrap_webpack_builder_specs',
|
||
|
'webpack:builder_specs',
|
||
|
'jasmine:builder:build',
|
||
|
'connect:specs_builder',
|
||
|
'watch:js_affected'
|
||
|
]);
|
||
|
|
||
|
/**
|
||
|
* `grunt dashboard_specs` compile dashboard specs
|
||
|
*/
|
||
|
grunt.registerTask('test:browser:dashboard', 'Build only dashboard specs', [
|
||
|
'generate_dashboard_specs',
|
||
|
'bootstrap_webpack_dashboard_specs',
|
||
|
'webpack:dashboard_specs',
|
||
|
'jasmine:dashboard:build',
|
||
|
'connect:specs_dashboard',
|
||
|
'watch:dashboard_specs'
|
||
|
]);
|
||
|
|
||
|
grunt.registerTask('setConfig', 'Set a config property', function (name, val) {
|
||
|
grunt.config.set(name, val);
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* `grunt affected_editor_specs` compile all Editor specs and launch a webpage in the browser.
|
||
|
*/
|
||
|
grunt.registerTask('affected_editor_specs', 'Build Editor specs', [
|
||
|
'jasmine:cartodbui:build',
|
||
|
'connect:server',
|
||
|
'watch:js_affected_editor'
|
||
|
]);
|
||
|
|
||
|
/**
|
||
|
* Delegate task to command line.
|
||
|
* @param {String} name - If taskname starts with npm it's run a npm script (i.e. `npm run foobar`
|
||
|
* @param {Object} d - d as in data
|
||
|
* @param {Array} d.args - arguments to pass to the d.cmd
|
||
|
* @param {String} [d.cmd = process.execPath]
|
||
|
* @param {String} [d.desc = ''] - description
|
||
|
* @param {...string} args space-separated arguments passed to the cmd
|
||
|
*/
|
||
|
function registerCmdTask (name, opts) {
|
||
|
opts = _.extend({
|
||
|
cmd: process.execPath,
|
||
|
desc: '',
|
||
|
args: []
|
||
|
}, opts);
|
||
|
grunt.registerTask(name, opts.desc, function () {
|
||
|
// adapted from http://stackoverflow.com/a/24796749
|
||
|
var done = this.async();
|
||
|
grunt.util.spawn({
|
||
|
cmd: opts.cmd,
|
||
|
args: opts.args,
|
||
|
opts: { stdio: 'inherit' }
|
||
|
}, done);
|
||
|
});
|
||
|
}
|
||
|
};
|