a0488b30a7
This commit gets rid of all references to Travis and, belatedly, Jenkins. Now all CI is done on CircleCI and releases are run locally. The CI no longer updates the docs and code.angularjs.org for jobs that are not on the `master` branch. During releases, the docs and code should be uploaded manually.
320 lines
11 KiB
JavaScript
320 lines
11 KiB
JavaScript
'use strict';
|
|
|
|
var fs = require('fs');
|
|
var shell = require('shelljs');
|
|
var grunt = require('grunt');
|
|
var spawn = require('npm-run').spawn;
|
|
|
|
var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP mode. */\n\n';
|
|
|
|
const docsScriptFolder = 'scripts/docs.angularjs.org-firebase';
|
|
|
|
module.exports = {
|
|
|
|
startKarma: function(config, singleRun, done) {
|
|
var browsers = grunt.option('browsers');
|
|
var reporters = grunt.option('reporters');
|
|
var noColor = grunt.option('no-colors');
|
|
var port = grunt.option('port');
|
|
var p = spawn('karma', ['start', config,
|
|
singleRun ? '--single-run=true' : '',
|
|
reporters ? '--reporters=' + reporters : '',
|
|
browsers ? '--browsers=' + browsers : '',
|
|
noColor ? '--no-colors' : '',
|
|
port ? '--port=' + port : ''
|
|
]);
|
|
p.stdout.pipe(process.stdout);
|
|
p.stderr.pipe(process.stderr);
|
|
p.on('exit', function(code) {
|
|
if (code !== 0) grunt.fail.warn('Karma test(s) failed. Exit code: ' + code);
|
|
done();
|
|
});
|
|
},
|
|
|
|
|
|
updateWebdriver: function(done) {
|
|
if (process.env.CI) {
|
|
// Skip the webdriver-manager update on CI, since the browsers will
|
|
// be provided remotely.
|
|
done();
|
|
return;
|
|
}
|
|
var p = spawn('webdriver-manager', ['update']);
|
|
p.stdout.pipe(process.stdout);
|
|
p.stderr.pipe(process.stderr);
|
|
p.on('exit', function(code) {
|
|
if (code !== 0) grunt.fail.warn('Webdriver failed to update');
|
|
done();
|
|
});
|
|
},
|
|
|
|
startProtractor: function(config, done) {
|
|
var sauceUser = grunt.option('sauceUser');
|
|
var sauceKey = grunt.option('sauceKey');
|
|
var tunnelIdentifier = grunt.option('capabilities.tunnel-identifier');
|
|
var sauceBuild = grunt.option('capabilities.build');
|
|
var browser = grunt.option('browser');
|
|
var specs = grunt.option('specs');
|
|
var args = [config];
|
|
if (sauceUser) args.push('--sauceUser=' + sauceUser);
|
|
if (sauceKey) args.push('--sauceKey=' + sauceKey);
|
|
if (tunnelIdentifier) args.push('--capabilities.tunnel-identifier=' + tunnelIdentifier);
|
|
if (sauceBuild) args.push('--capabilities.build=' + sauceBuild);
|
|
if (specs) args.push('--specs=' + specs);
|
|
if (browser) {
|
|
args.push('--browser=' + browser);
|
|
}
|
|
|
|
|
|
var p = spawn('protractor', args);
|
|
p.stdout.pipe(process.stdout);
|
|
p.stderr.pipe(process.stderr);
|
|
p.on('exit', function(code) {
|
|
if (code !== 0) grunt.fail.warn('Protractor test(s) failed. Exit code: ' + code);
|
|
done();
|
|
});
|
|
},
|
|
|
|
|
|
wrap(src, name) {
|
|
return [`src/${name}.prefix`, ...src, `src/${name}.suffix`];
|
|
},
|
|
|
|
|
|
addStyle: function(src, styles, minify) {
|
|
styles = styles.reduce(processCSS.bind(this), {
|
|
js: [src],
|
|
css: []
|
|
});
|
|
return {
|
|
js: styles.js.join('\n'),
|
|
css: styles.css.join('\n')
|
|
};
|
|
|
|
function processCSS(state, file) {
|
|
var css = fs.readFileSync(file).toString(),
|
|
js;
|
|
state.css.push(css);
|
|
|
|
if (minify) {
|
|
css = css
|
|
.replace(/\r?\n/g, '')
|
|
.replace(/\/\*.*?\*\//g, '')
|
|
.replace(/:\s+/g, ':')
|
|
.replace(/\s*\{\s*/g, '{')
|
|
.replace(/\s*\}\s*/g, '}')
|
|
.replace(/\s*,\s*/g, ',')
|
|
.replace(/\s*;\s*/g, ';');
|
|
}
|
|
//escape for js
|
|
css = css
|
|
.replace(/\\/g, '\\\\')
|
|
.replace(/'/g, '\\\'')
|
|
.replace(/\r?\n/g, '\\n');
|
|
js = '!window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend(\'<style type="text/css">' + css + '</style>\');';
|
|
state.js.push(js);
|
|
|
|
return state;
|
|
}
|
|
},
|
|
|
|
|
|
process: function(src, NG_VERSION, strict) {
|
|
var processed = src
|
|
.replace(/(['"])NG_VERSION_FULL\1/g, NG_VERSION.full)
|
|
.replace(/(['"])NG_VERSION_MAJOR\1/, NG_VERSION.major)
|
|
.replace(/(['"])NG_VERSION_MINOR\1/, NG_VERSION.minor)
|
|
.replace(/(['"])NG_VERSION_DOT\1/, NG_VERSION.patch)
|
|
.replace(/(['"])NG_VERSION_CDN\1/, NG_VERSION.cdn)
|
|
.replace(/(['"])NG_VERSION_CODENAME\1/, NG_VERSION.codeName);
|
|
if (strict !== false) processed = this.singleStrict(processed, '\n\n', true);
|
|
return processed;
|
|
},
|
|
|
|
|
|
build: function(config, fn) {
|
|
var files = grunt.file.expand(config.src);
|
|
// grunt.file.expand might reorder the list of files
|
|
// when it is expanding globs, so we use prefix and suffix
|
|
// fields to ensure that files are at the start of end of
|
|
// the list (primarily for wrapping in an IIFE).
|
|
if (config.prefix) {
|
|
files = grunt.file.expand(config.prefix).concat(files);
|
|
}
|
|
if (config.suffix) {
|
|
files = files.concat(grunt.file.expand(config.suffix));
|
|
}
|
|
var styles = config.styles;
|
|
var processedStyles;
|
|
//concat
|
|
var src = files.map(function(filepath) {
|
|
return grunt.file.read(filepath);
|
|
}).join(grunt.util.normalizelf('\n'));
|
|
//process
|
|
var processed = this.process(src, grunt.config('NG_VERSION'), config.strict);
|
|
if (styles) {
|
|
processedStyles = this.addStyle(processed, styles.css, styles.minify);
|
|
processed = processedStyles.js;
|
|
if (config.styles.generateCspCssFile) {
|
|
grunt.file.write(removeSuffix(config.dest) + '-csp.css', CSP_CSS_HEADER + processedStyles.css);
|
|
}
|
|
}
|
|
//write
|
|
grunt.file.write(config.dest, processed);
|
|
grunt.log.ok('File ' + config.dest + ' created.');
|
|
fn();
|
|
|
|
function removeSuffix(fileName) {
|
|
return fileName.replace(/\.js$/, '');
|
|
}
|
|
},
|
|
|
|
|
|
singleStrict: function(src, insert) {
|
|
return src
|
|
.replace(/\s*("|')use strict("|');\s*/g, insert) // remove all file-specific strict mode flags
|
|
.replace(/(\(function\([^)]*\)\s*\{)/, '$1\'use strict\';'); // add single strict mode flag
|
|
},
|
|
|
|
|
|
sourceMap: function(mapFile, fileContents) {
|
|
var sourceMapLine = '//# sourceMappingURL=' + mapFile + '\n';
|
|
return fileContents + sourceMapLine;
|
|
},
|
|
|
|
|
|
min: function(file, done) {
|
|
var classPathSep = (process.platform === 'win32') ? ';' : ':';
|
|
var minFile = file.replace(/\.js$/, '.min.js');
|
|
var mapFile = minFile + '.map';
|
|
var mapFileName = mapFile.match(/[^/]+$/)[0];
|
|
var errorFileName = file.replace(/\.js$/, '-errors.json');
|
|
var versionNumber = grunt.config('NG_VERSION').full;
|
|
var compilationLevel = (file === 'build/angular-message-format.js') ?
|
|
'ADVANCED_OPTIMIZATIONS' : 'SIMPLE_OPTIMIZATIONS';
|
|
shell.exec(
|
|
'java ' +
|
|
this.java32flags() + ' ' +
|
|
this.memoryRequirement() + ' ' +
|
|
'-cp vendor/closure-compiler/compiler.jar' + classPathSep +
|
|
'vendor/ng-closure-runner/ngcompiler.jar ' +
|
|
'org.angularjs.closurerunner.NgClosureRunner ' +
|
|
'--compilation_level ' + compilationLevel + ' ' +
|
|
'--language_in ECMASCRIPT5_STRICT ' +
|
|
'--minerr_pass ' +
|
|
'--minerr_errors ' + errorFileName + ' ' +
|
|
'--minerr_url http://errors.angularjs.org/' + versionNumber + '/ ' +
|
|
'--source_map_format=V3 ' +
|
|
'--create_source_map ' + mapFile + ' ' +
|
|
'--js ' + file + ' ' +
|
|
'--js_output_file ' + minFile,
|
|
function(code) {
|
|
if (code !== 0) grunt.fail.warn('Error minifying ' + file);
|
|
|
|
// closure creates the source map relative to build/ folder, we need to strip those references
|
|
grunt.file.write(mapFile, grunt.file.read(mapFile).replace('"file":"build/', '"file":"').
|
|
replace('"sources":["build/','"sources":["'));
|
|
|
|
// move add use strict into the closure + add source map pragma
|
|
grunt.file.write(minFile, this.sourceMap(mapFileName, this.singleStrict(grunt.file.read(minFile), '\n')));
|
|
grunt.log.ok(file + ' minified into ' + minFile);
|
|
done();
|
|
}.bind(this));
|
|
},
|
|
|
|
memoryRequirement: function() {
|
|
return (process.platform === 'win32') ? '' : '-Xmx2g';
|
|
},
|
|
|
|
|
|
//returns the 32-bit mode force flags for java compiler if supported, this makes the build much faster
|
|
java32flags: function() {
|
|
if (process.platform === 'win32') return '';
|
|
if (shell.exec('java -d32 -version 2>&1', {silent: true}).code !== 0) return '';
|
|
return ' -d32 -client';
|
|
},
|
|
|
|
|
|
//collects and combines error messages stripped out in minify step
|
|
collectErrors: function() {
|
|
var combined = {
|
|
id: 'ng',
|
|
generated: new Date().toString(),
|
|
errors: {}
|
|
};
|
|
grunt.file.expand('build/*-errors.json').forEach(function(file) {
|
|
var errors = grunt.file.readJSON(file),
|
|
namespace;
|
|
Object.keys(errors).forEach(function(prop) {
|
|
if (typeof errors[prop] === 'object') {
|
|
namespace = errors[prop];
|
|
if (combined.errors[prop]) {
|
|
Object.keys(namespace).forEach(function(code) {
|
|
if (combined.errors[prop][code] && combined.errors[prop][code] !== namespace[code]) {
|
|
grunt.warn('[collect-errors] Duplicate minErr codes don\'t match!');
|
|
} else {
|
|
combined.errors[prop][code] = namespace[code];
|
|
}
|
|
});
|
|
} else {
|
|
combined.errors[prop] = namespace;
|
|
}
|
|
} else {
|
|
if (combined.errors[prop] && combined.errors[prop] !== errors[prop]) {
|
|
grunt.warn('[collect-errors] Duplicate minErr codes don\'t match!');
|
|
} else {
|
|
combined.errors[prop] = errors[prop];
|
|
}
|
|
}
|
|
});
|
|
});
|
|
grunt.file.write('build/errors.json', JSON.stringify(combined));
|
|
grunt.file.expand('build/*-errors.json').forEach(grunt.file.delete);
|
|
},
|
|
|
|
|
|
//csp connect middleware
|
|
conditionalCsp: function() {
|
|
return function(req, res, next) {
|
|
var CSP = /\.csp\W/;
|
|
|
|
if (CSP.test(req.url)) {
|
|
res.setHeader('X-WebKit-CSP', 'default-src \'self\';');
|
|
res.setHeader('X-Content-Security-Policy', 'default-src \'self\'');
|
|
res.setHeader('Content-Security-Policy', 'default-src \'self\'');
|
|
}
|
|
next();
|
|
};
|
|
},
|
|
|
|
|
|
//rewrite connect middleware
|
|
rewrite: function() {
|
|
return function(req, res, next) {
|
|
var REWRITE = /\/(guide|api|cookbook|misc|tutorial|error).*$/,
|
|
IGNORED = /(\.(css|js|png|jpg|gif|svg)$|partials\/.*\.html$)/,
|
|
match;
|
|
|
|
if (!IGNORED.test(req.url) && (match = req.url.match(REWRITE))) {
|
|
console.log('rewriting', req.url);
|
|
req.url = req.url.replace(match[0], '/index.html');
|
|
}
|
|
next();
|
|
};
|
|
},
|
|
|
|
docsScriptFolder,
|
|
|
|
// Our Firebase projects are in subfolders, but the firebase tool expects them in the root,
|
|
// so we need to modify the upload folder path and copy the file into the root
|
|
firebaseDocsJsonForCI: function() {
|
|
var fileName = docsScriptFolder + '/firebase.json';
|
|
var json = grunt.file.readJSON(fileName);
|
|
|
|
(json.hosting || (json.hosting = {})).public = 'deploy/docs';
|
|
(json.functions || (json.functions = {})).source = docsScriptFolder + '/functions';
|
|
|
|
grunt.file.write('firebase.json', JSON.stringify(json));
|
|
}
|
|
};
|