build: add --projects a.k.a the turbo button
we can now filter build graph via --project flag to speed up build performance usage: gulp test.unit.js --project=angular2,angular2_material Closes #5272
This commit is contained in:
parent
2ecbd0eaaf
commit
1d9c44b34f
62
gulpfile.js
62
gulpfile.js
|
@ -46,10 +46,36 @@ var dartSdk = require('./tools/build/dart');
|
|||
var browserProvidersConf = require('./browser-providers.conf.js');
|
||||
var os = require('os');
|
||||
|
||||
require('./tools/check-environment')({
|
||||
requiredNpmVersion: '>=2.14.7',
|
||||
requiredNodeVersion: '>=4.2.1'
|
||||
});
|
||||
require('./tools/check-environment')(
|
||||
{requiredNpmVersion: '>=2.14.7', requiredNodeVersion: '>=4.2.1'});
|
||||
|
||||
var cliArgs = minimist(process.argv.slice(2));
|
||||
|
||||
if (cliArgs.projects) {
|
||||
// normalize for analytics
|
||||
cliArgs.projects.split(',').sort().join(',');
|
||||
}
|
||||
|
||||
// --projects=angular2,angular2_material => {angular2: true, angular2_material: true}
|
||||
var allProjects =
|
||||
'angular1_router,angular2,angular2_material,benchmarks,benchmarks_external,benchpress,playground';
|
||||
var cliArgsProjects = (cliArgs.projects || allProjects)
|
||||
.split(',')
|
||||
.reduce((map, projectName) => {
|
||||
map[projectName] = true;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
function printModulesWarning() {
|
||||
if (!cliArgs.projects && !process.env.CI) {
|
||||
// if users didn't specify projects to build, tell them why and how they should
|
||||
console.warn(
|
||||
"Pro Tip: Did you know that you can speed up your build by specifying project name(s)?");
|
||||
console.warn(" It's like pressing the turbo button in the old days, but better!");
|
||||
console.warn(" Examples: --project=angular2 or --project=angular2,angular2_material");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Make it easy to quiet down portions of the build.
|
||||
// --logs=all -> log everything (This is the default)
|
||||
|
@ -162,7 +188,7 @@ gulp.task('build/tree.dart', ['build/clean.dart', 'build.tools'],
|
|||
|
||||
|
||||
gulp.task('!build/tree.dart',
|
||||
function() { return angularBuilder.rebuildDartTree(); });
|
||||
function() { return angularBuilder.rebuildDartTree(cliArgsProjects); });
|
||||
|
||||
|
||||
// ------------
|
||||
|
@ -444,15 +470,19 @@ function getBrowsersFromCLI(provider) {
|
|||
}
|
||||
|
||||
gulp.task('test.unit.js', ['build.js.dev'], function(done) {
|
||||
printModulesWarning();
|
||||
runSequence('!test.unit.js/karma-server', function() {
|
||||
watch('modules/**', {ignoreInitial: true}, ['!broccoli.js.dev', '!test.unit.js/karma-run']);
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('watch.js.dev', ['build.js.dev'],
|
||||
function(done) { watch('modules/**', ['!broccoli.js.dev']); });
|
||||
gulp.task('watch.js.dev', ['build.js.dev'], function(done) {
|
||||
printModulesWarning();
|
||||
watch('modules/**', ['!broccoli.js.dev']);
|
||||
});
|
||||
|
||||
gulp.task('test.unit.js.sauce', ['build.js.dev'], function(done) {
|
||||
printModulesWarning();
|
||||
var browserConf = getBrowsersFromCLI('SL');
|
||||
if (browserConf.isProvider) {
|
||||
launchKarmaWithExternalBrowsers(['dots'], browserConf.browsersToRun, done);
|
||||
|
@ -462,6 +492,7 @@ gulp.task('test.unit.js.sauce', ['build.js.dev'], function(done) {
|
|||
});
|
||||
|
||||
gulp.task('test.unit.js.browserstack', ['build.js.dev'], function(done) {
|
||||
printModulesWarning();
|
||||
var browserConf = getBrowsersFromCLI('BS');
|
||||
if (browserConf.isProvider) {
|
||||
launchKarmaWithExternalBrowsers(['dots'], browserConf.browsersToRun, done);
|
||||
|
@ -535,6 +566,7 @@ gulp.task('!test.unit.router/karma-run', function(done) {
|
|||
gulp.task('buildRouter.dev', function() { buildRouter(); });
|
||||
|
||||
gulp.task('test.unit.dart', function(done) {
|
||||
printModulesWarning();
|
||||
runSequence('build/tree.dart', 'build/pure-packages.dart', '!build/pubget.angular2.dart',
|
||||
'!build/change_detect.dart', '!build/remove-pub-symlinks', 'build.dart.material.css',
|
||||
'!test.unit.dart/karma-server', '!test.unit.dart/karma-run', function(error) {
|
||||
|
@ -632,12 +664,9 @@ gulp.task('test.unit.cjs/ci', function(done) {
|
|||
|
||||
|
||||
gulp.task('test.unit.cjs', ['build/clean.js', 'build.tools'], function(neverDone) {
|
||||
|
||||
printModulesWarning();
|
||||
treatTestErrorsAsFatal = false;
|
||||
|
||||
var buildAndTest = ['!build.js.cjs', 'test.unit.cjs/ci'];
|
||||
|
||||
watch('modules/**', buildAndTest);
|
||||
watch('modules/**', ['!build.js.cjs', 'test.unit.cjs/ci']);
|
||||
});
|
||||
|
||||
// Use this target to continuously run dartvm unit-tests (such as transformer
|
||||
|
@ -812,12 +841,11 @@ gulp.task('!build.tools', function() {
|
|||
gulp.task('broccoli.js.dev', ['build.tools'],
|
||||
function(done) { runSequence('!broccoli.js.dev', sequenceComplete(done)); });
|
||||
|
||||
gulp.task('!broccoli.js.dev', () => {
|
||||
return angularBuilder.rebuildBrowserDevTree();
|
||||
});
|
||||
gulp.task('!broccoli.js.dev',
|
||||
() => { return angularBuilder.rebuildBrowserDevTree(cliArgsProjects); });
|
||||
|
||||
gulp.task('!broccoli.js.prod',
|
||||
function() { return angularBuilder.rebuildBrowserProdTree(); });
|
||||
function() { return angularBuilder.rebuildBrowserProdTree(cliArgsProjects); });
|
||||
|
||||
gulp.task('build.js.dev', ['build/clean.js'], function(done) {
|
||||
runSequence('broccoli.js.dev', 'build.css.material', sequenceComplete(done));
|
||||
|
@ -840,7 +868,7 @@ var firstBuildJsCjs = true;
|
|||
* private task
|
||||
*/
|
||||
gulp.task('!build.js.cjs', function() {
|
||||
return angularBuilder.rebuildNodeTree()
|
||||
return angularBuilder.rebuildNodeTree(cliArgsProjects)
|
||||
.then(function() {
|
||||
if (firstBuildJsCjs) {
|
||||
firstBuildJsCjs = false;
|
||||
|
|
|
@ -8,8 +8,14 @@
|
|||
/// <reference path="../typings/hammerjs/hammerjs.d.ts"/>
|
||||
/// <reference path="../typings/jasmine/jasmine.d.ts"/>
|
||||
/// <reference path="../typings/angular-protractor/angular-protractor.d.ts"/>
|
||||
|
||||
// TODO: ideally the node.d.ts reference should be scoped only for files that need and not to all
|
||||
// the code including client code
|
||||
/// <reference path="../typings/node/node.d.ts" />
|
||||
|
||||
declare var assert: any;
|
||||
|
||||
|
||||
interface BrowserNodeGlobal {
|
||||
Object: typeof Object;
|
||||
Array: typeof Array;
|
||||
|
|
|
@ -7,6 +7,10 @@ var path = require('path');
|
|||
var printSlowTrees = require('broccoli-slow-trees');
|
||||
var Q = require('q');
|
||||
|
||||
type ProjectMap = {
|
||||
[key: string]: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* BroccoliBuilder facade for all of our build pipelines.
|
||||
*/
|
||||
|
@ -21,26 +25,26 @@ export class AngularBuilder {
|
|||
constructor(public options: AngularBuilderOptions) { this.outputPath = options.outputPath; }
|
||||
|
||||
|
||||
public rebuildBrowserDevTree(): Promise<BuildResult> {
|
||||
this.browserDevBuilder = this.browserDevBuilder || this.makeBrowserDevBuilder();
|
||||
public rebuildBrowserDevTree(projects: ProjectMap): Promise<BuildResult> {
|
||||
this.browserDevBuilder = this.browserDevBuilder || this.makeBrowserDevBuilder(projects);
|
||||
return this.rebuild(this.browserDevBuilder, 'js.dev');
|
||||
}
|
||||
|
||||
|
||||
public rebuildBrowserProdTree(): Promise<BuildResult> {
|
||||
this.browserProdBuilder = this.browserProdBuilder || this.makeBrowserProdBuilder();
|
||||
public rebuildBrowserProdTree(projects: ProjectMap): Promise<BuildResult> {
|
||||
this.browserProdBuilder = this.browserProdBuilder || this.makeBrowserProdBuilder(projects);
|
||||
return this.rebuild(this.browserProdBuilder, 'js.prod');
|
||||
}
|
||||
|
||||
|
||||
public rebuildNodeTree(): Promise<BuildResult> {
|
||||
this.nodeBuilder = this.nodeBuilder || this.makeNodeBuilder();
|
||||
public rebuildNodeTree(projects: ProjectMap): Promise<BuildResult> {
|
||||
this.nodeBuilder = this.nodeBuilder || this.makeNodeBuilder(projects);
|
||||
return this.rebuild(this.nodeBuilder, 'js.cjs');
|
||||
}
|
||||
|
||||
|
||||
public rebuildDartTree(): Promise<BuildResult> {
|
||||
this.dartBuilder = this.dartBuilder || this.makeDartBuilder();
|
||||
public rebuildDartTree(projects: ProjectMap): Promise<BuildResult> {
|
||||
this.dartBuilder = this.dartBuilder || this.makeDartBuilder(projects);
|
||||
return this.rebuild(this.dartBuilder, 'dart');
|
||||
}
|
||||
|
||||
|
@ -54,31 +58,32 @@ export class AngularBuilder {
|
|||
}
|
||||
|
||||
|
||||
private makeBrowserDevBuilder(): BroccoliBuilder {
|
||||
let tree = makeBrowserTree({name: 'dev', typeAssertions: true},
|
||||
private makeBrowserDevBuilder(projects: ProjectMap): BroccoliBuilder {
|
||||
let tree = makeBrowserTree({name: 'dev', typeAssertions: true, projects: projects},
|
||||
path.join(this.outputPath, 'js', 'dev'));
|
||||
return new broccoli.Builder(tree);
|
||||
}
|
||||
|
||||
|
||||
private makeBrowserProdBuilder(): BroccoliBuilder {
|
||||
let tree = makeBrowserTree({name: 'prod', typeAssertions: false},
|
||||
private makeBrowserProdBuilder(projects: ProjectMap): BroccoliBuilder {
|
||||
let tree = makeBrowserTree({name: 'prod', typeAssertions: false, projects: projects},
|
||||
path.join(this.outputPath, 'js', 'prod'));
|
||||
return new broccoli.Builder(tree);
|
||||
}
|
||||
|
||||
|
||||
private makeNodeBuilder(): BroccoliBuilder {
|
||||
let tree = makeNodeTree(path.join(this.outputPath, 'js', 'cjs'));
|
||||
private makeNodeBuilder(projects: ProjectMap): BroccoliBuilder {
|
||||
let tree = makeNodeTree(projects, path.join(this.outputPath, 'js', 'cjs'));
|
||||
return new broccoli.Builder(tree);
|
||||
}
|
||||
|
||||
|
||||
private makeDartBuilder(): BroccoliBuilder {
|
||||
private makeDartBuilder(projects: ProjectMap): BroccoliBuilder {
|
||||
let options = {
|
||||
outputPath: path.join(this.outputPath, 'dart'),
|
||||
dartSDK: this.options.dartSDK,
|
||||
logs: this.options.logs
|
||||
logs: this.options.logs,
|
||||
projects: projects
|
||||
};
|
||||
let tree = makeDartTree(options);
|
||||
return new broccoli.Builder(tree);
|
||||
|
|
|
@ -47,6 +47,12 @@ class DiffingTSCompiler implements DiffingBroccoliPlugin {
|
|||
// in tsc 1.7.x this api was renamed to parseJsonConfigFileContent
|
||||
// the conversion is a bit awkward, see https://github.com/Microsoft/TypeScript/issues/5276
|
||||
this.tsOpts = ts.parseConfigFile({compilerOptions: options, files: []}, null, null).options;
|
||||
|
||||
// TODO: the above turns rootDir set to './' into an empty string - looks like a tsc bug
|
||||
// check back when we upgrade to 1.7.x
|
||||
if (this.tsOpts.rootDir === '') {
|
||||
this.tsOpts.rootDir = './';
|
||||
}
|
||||
this.tsOpts.outDir = this.cachePath;
|
||||
|
||||
this.tsServiceHost = new CustomLanguageServiceHost(this.tsOpts, this.rootFilePaths,
|
||||
|
|
|
@ -70,26 +70,55 @@ const kServedPaths = [
|
|||
|
||||
|
||||
module.exports = function makeBrowserTree(options, destinationPath) {
|
||||
var modulesTree = new Funnel('modules', {
|
||||
include: ['**/**'],
|
||||
exclude: [
|
||||
'**/*.cjs',
|
||||
'benchmarks/e2e_test/**',
|
||||
'angular1_router/**',
|
||||
// Exclude ES6 polyfill typings when tsc target=ES6
|
||||
'angular2/typings/es6-*/**',
|
||||
],
|
||||
destDir: '/'
|
||||
});
|
||||
var modules = options.projects;
|
||||
|
||||
if (modules.angular2) {
|
||||
var angular2Tree = new Funnel('modules/angular2', {
|
||||
include: ['**/**'],
|
||||
exclude: [
|
||||
// Exclude ES6 polyfill typings when tsc target=ES6
|
||||
'typings/es6-*/**',
|
||||
],
|
||||
destDir: '/angular2/'
|
||||
});
|
||||
}
|
||||
|
||||
if (modules.angular2_material) {
|
||||
var angular2MaterialTree =
|
||||
new Funnel('modules/angular2_material',
|
||||
{include: ['**/**'], exclude: ['e2e_test/**'], destDir: '/angular2_material/'});
|
||||
}
|
||||
|
||||
if (modules.benchmarks) {
|
||||
var benchmarksTree =
|
||||
new Funnel('modules/benchmarks',
|
||||
{include: ['**/**'], exclude: ['e2e_test/**'], destDir: '/benchmarks/'});
|
||||
}
|
||||
|
||||
if (modules.benchmarks_external) {
|
||||
var benchmarksExternalTree = new Funnel(
|
||||
'modules/benchmarks_external',
|
||||
{include: ['**/**'], exclude: ['e2e_test/**'], destDir: '/benchmarks_external/'});
|
||||
}
|
||||
|
||||
if (modules.playground) {
|
||||
var playgroundTree =
|
||||
new Funnel('modules/playground',
|
||||
{include: ['**/**'], exclude: ['e2e_test/**'], destDir: '/playground/'});
|
||||
}
|
||||
|
||||
var modulesTree = mergeTrees(
|
||||
[angular2Tree, angular2MaterialTree, benchmarksTree, benchmarksExternalTree, playgroundTree]);
|
||||
|
||||
var clientModules = new Funnel(
|
||||
'node_modules', {include: ['@reactivex/**/**', 'parse5/**/**', 'css/**/**'], destDir: '/'});
|
||||
|
||||
var es5ModulesTree = new Funnel('modules', {
|
||||
include: ['**/**'],
|
||||
exclude: ['**/*.cjs', 'angular1_router/**', 'benchmarks/e2e_test/**'],
|
||||
destDir: '/'
|
||||
});
|
||||
var es6PolyfillTypings =
|
||||
new Funnel('modules', {include: ['angular2/typings/es6-*/**'], destDir: '/'});
|
||||
|
||||
var es5ModulesTree = mergeTrees([modulesTree, es6PolyfillTypings]);
|
||||
|
||||
es5ModulesTree = stew.debug(es5ModulesTree, {name: 'debug-es5'});
|
||||
|
||||
var scriptPathPatternReplacement = {
|
||||
match: '@@PATH',
|
||||
|
@ -119,7 +148,7 @@ module.exports = function makeBrowserTree(options, destinationPath) {
|
|||
experimentalDecorators: true,
|
||||
mapRoot: '', // force sourcemaps to use relative path
|
||||
noEmitOnError: false,
|
||||
rootDir: '.',
|
||||
rootDir: './',
|
||||
rootFilePaths: ['angular2/manual_typings/globals-es6.d.ts'],
|
||||
sourceMap: true,
|
||||
sourceRoot: '.',
|
||||
|
@ -127,7 +156,7 @@ module.exports = function makeBrowserTree(options, destinationPath) {
|
|||
});
|
||||
|
||||
// Use TypeScript to transpile the *.ts files to ES5
|
||||
var typescriptOptions = {
|
||||
var es5Tree = compileWithTypescript(es5ModulesTree, {
|
||||
declaration: false,
|
||||
emitDecoratorMetadata: true,
|
||||
experimentalDecorators: true,
|
||||
|
@ -135,13 +164,12 @@ module.exports = function makeBrowserTree(options, destinationPath) {
|
|||
module: 'commonjs',
|
||||
moduleResolution: 'classic',
|
||||
noEmitOnError: true,
|
||||
rootDir: '.',
|
||||
rootDir: './',
|
||||
rootFilePaths: ['angular2/manual_typings/globals.d.ts'],
|
||||
sourceMap: true,
|
||||
sourceRoot: '.',
|
||||
target: 'es5'
|
||||
};
|
||||
var es5Tree = compileWithTypescript(es5ModulesTree, typescriptOptions);
|
||||
});
|
||||
|
||||
var vendorScriptsTree = flatten(new Funnel('.', {
|
||||
files: [
|
||||
|
@ -173,61 +201,80 @@ module.exports = function makeBrowserTree(options, destinationPath) {
|
|||
return funnels;
|
||||
}
|
||||
|
||||
|
||||
if (modules.angular2_material || modules.benchmarks || modules.benchmarks_external ||
|
||||
modules.playground) {
|
||||
var assetsTree = new Funnel(
|
||||
modulesTree, {include: ['**/*'], exclude: ['**/*.{html,ts,dart}'], destDir: '/'});
|
||||
}
|
||||
|
||||
var htmlTree = new Funnel(
|
||||
modulesTree, {include: ['*/src/**/*.html', '**/playground/**/*.html'], destDir: '/'});
|
||||
htmlTree = replace(htmlTree, {
|
||||
files: ['playground*/**/*.html'],
|
||||
patterns: [
|
||||
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS')},
|
||||
scriptPathPatternReplacement,
|
||||
scriptFilePatternReplacement
|
||||
]
|
||||
});
|
||||
|
||||
if (modules.benchmarks || modules.benchmarks_external || modules.playground) {
|
||||
htmlTree = replace(htmlTree, {
|
||||
files: ['playground*/**/*.html'],
|
||||
patterns: [
|
||||
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS')},
|
||||
scriptPathPatternReplacement,
|
||||
scriptFilePatternReplacement
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
htmlTree = replace(htmlTree, {
|
||||
files: ['benchmarks/**'],
|
||||
patterns: [
|
||||
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS_benchmarks')},
|
||||
scriptPathPatternReplacement,
|
||||
scriptFilePatternReplacement
|
||||
]
|
||||
});
|
||||
if (modules.benchmarks) {
|
||||
htmlTree = replace(htmlTree, {
|
||||
files: ['benchmarks/**'],
|
||||
patterns: [
|
||||
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS_benchmarks')},
|
||||
scriptPathPatternReplacement,
|
||||
scriptFilePatternReplacement
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
htmlTree = replace(htmlTree, {
|
||||
files: ['benchmarks_external/**'],
|
||||
patterns: [
|
||||
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS_benchmarks_external')},
|
||||
scriptPathPatternReplacement,
|
||||
scriptFilePatternReplacement
|
||||
]
|
||||
});
|
||||
if (modules.benchmarks_external) {
|
||||
htmlTree = replace(htmlTree, {
|
||||
files: ['benchmarks_external/**'],
|
||||
patterns: [
|
||||
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS_benchmarks_external')},
|
||||
scriptPathPatternReplacement,
|
||||
scriptFilePatternReplacement
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// We need to replace the regular angular bundle with the web-worker bundle
|
||||
// for web-worker e2e tests.
|
||||
htmlTree = replace(htmlTree, {
|
||||
files: ['playground*/**/web_workers/**/*.html'],
|
||||
patterns: [{match: "/bundle/angular2.dev.js", replacement: "/bundle/web_worker/ui.dev.js"}]
|
||||
});
|
||||
if (modules.playground) {
|
||||
// We need to replace the regular angular bundle with the web-worker bundle
|
||||
// for web-worker e2e tests.
|
||||
htmlTree = replace(htmlTree, {
|
||||
files: ['playground*/**/web_workers/**/*.html'],
|
||||
patterns: [{match: "/bundle/angular2.dev.js", replacement: "/bundle/web_worker/ui.dev.js"}]
|
||||
});
|
||||
}
|
||||
|
||||
var assetsTree =
|
||||
new Funnel(modulesTree, {include: ['**/*'], exclude: ['**/*.{html,ts,dart}'], destDir: '/'});
|
||||
if (modules.benchmarks || modules.benchmarks_external) {
|
||||
var scripts = mergeTrees(servingTrees);
|
||||
}
|
||||
|
||||
var scripts = mergeTrees(servingTrees);
|
||||
var polymerFiles = new Funnel('.', {
|
||||
files: [
|
||||
'bower_components/polymer/polymer.html',
|
||||
'bower_components/polymer/polymer-micro.html',
|
||||
'bower_components/polymer/polymer-mini.html',
|
||||
'tools/build/snippets/url_params_to_form.js'
|
||||
]
|
||||
});
|
||||
var polymer = stew.mv(flatten(polymerFiles), 'benchmarks_external/src/tree/polymer');
|
||||
if (modules.benchmarks_external) {
|
||||
var polymerFiles = new Funnel('.', {
|
||||
files: [
|
||||
'bower_components/polymer/polymer.html',
|
||||
'bower_components/polymer/polymer-micro.html',
|
||||
'bower_components/polymer/polymer-mini.html',
|
||||
'tools/build/snippets/url_params_to_form.js'
|
||||
]
|
||||
});
|
||||
var polymer = stew.mv(flatten(polymerFiles), 'benchmarks_external/src/tree/polymer');
|
||||
|
||||
var reactFiles = new Funnel('.', {files: ['node_modules/react/dist/react.min.js']});
|
||||
var react = stew.mv(flatten(reactFiles), 'benchmarks_external/src/tree/react');
|
||||
var reactFiles = new Funnel('.', {files: ['node_modules/react/dist/react.min.js']});
|
||||
var react = stew.mv(flatten(reactFiles), 'benchmarks_external/src/tree/react');
|
||||
}
|
||||
|
||||
htmlTree = mergeTrees([htmlTree, scripts, polymer, react]);
|
||||
if (modules.benchmarks || modules.benchmarks_external || modules.playground) {
|
||||
htmlTree = mergeTrees([htmlTree, scripts, polymer, react]);
|
||||
}
|
||||
|
||||
es5Tree = mergeTrees([es5Tree, htmlTree, assetsTree, clientModules]);
|
||||
es6Tree = mergeTrees([es6Tree, htmlTree, assetsTree, clientModules]);
|
||||
|
|
|
@ -12,7 +12,7 @@ var stew = require('broccoli-stew');
|
|||
var projectRootDir = path.normalize(path.join(__dirname, '..', '..', '..', '..'));
|
||||
|
||||
|
||||
module.exports = function makeNodeTree(destinationPath) {
|
||||
module.exports = function makeNodeTree(projects, destinationPath) {
|
||||
// list of npm packages that this build will create
|
||||
var outputPackages = ['angular2', 'benchpress'];
|
||||
|
||||
|
|
Loading…
Reference in New Issue