chore(tools): remove unused files
This commit is contained in:
parent
e73ac1e992
commit
cdb3678fe3
|
@ -1,176 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let execSync = require('child_process').execSync;
|
|
||||||
let fs = require('fs');
|
|
||||||
|
|
||||||
let minimist;
|
|
||||||
try {
|
|
||||||
minimist = require('minimist');
|
|
||||||
} catch (e) {
|
|
||||||
minimist = function(){ return {projects: ""}; };
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = require('path');
|
|
||||||
let os = require('os');
|
|
||||||
let ua;
|
|
||||||
|
|
||||||
try {
|
|
||||||
ua = require('universal-analytics');
|
|
||||||
} catch(e) {
|
|
||||||
// ignore errors due to invoking analytics before the first npm install
|
|
||||||
}
|
|
||||||
|
|
||||||
const analyticsFile = path.resolve(path.join(__dirname, '..', '..', '.build-analytics'));
|
|
||||||
const analyticsId = "UA-8594346-17"; // Owned by the Angular account
|
|
||||||
const analyticsOptions = {
|
|
||||||
https: true,
|
|
||||||
debug: false
|
|
||||||
};
|
|
||||||
|
|
||||||
let cid = fs.existsSync(analyticsFile) ? fs.readFileSync(analyticsFile, 'utf-8') : null;
|
|
||||||
let visitor;
|
|
||||||
|
|
||||||
if (ua) {
|
|
||||||
if (cid) {
|
|
||||||
visitor = ua(analyticsId, cid, analyticsOptions);
|
|
||||||
} else {
|
|
||||||
visitor = ua(analyticsId, analyticsOptions);
|
|
||||||
cid = visitor.cid;
|
|
||||||
fs.writeFileSync(analyticsFile, cid, 'utf-8');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters
|
|
||||||
let customParams = {
|
|
||||||
// OS Platform (darwin, win32, linux)
|
|
||||||
cd1: os.platform(),
|
|
||||||
// Node.js Version (v4.1.2)
|
|
||||||
cd2: process.version,
|
|
||||||
// npm Version (2.14.7)
|
|
||||||
cd10: getNpmVersion(),
|
|
||||||
// TypeScript Version (1.6.2)
|
|
||||||
cd3: getTypeScriptVersion(),
|
|
||||||
// Dart Version
|
|
||||||
cd11: getDartVersion(),
|
|
||||||
// Dev Environment
|
|
||||||
cd4: process.env.TRAVIS ? 'Travis CI' : 'Local Dev',
|
|
||||||
// Travis - Pull Request?
|
|
||||||
cd5: (process.env.TRAVIS_PULL_REQUEST == 'true') ? 'true' : 'false',
|
|
||||||
// Travis - Branch Name (master)
|
|
||||||
cd6: process.env.TRAVIS_BRANCH,
|
|
||||||
// Travis - Repo Slug (angular/angular)
|
|
||||||
cd7: process.env.TRAVIS_REPO_SLUG,
|
|
||||||
// Travis - Job ID (1, 2, 3, 4, ...)
|
|
||||||
cd12: process.env.TRAVIS_JOB_NUMBER ? process.env.TRAVIS_JOB_NUMBER.split('.')[1] : undefined,
|
|
||||||
// HW - CPU Info
|
|
||||||
cd8: `${os.cpus().length} x ${os.cpus()[0].model}`,
|
|
||||||
// HW - Memory Info
|
|
||||||
cd9: `${Math.round(os.totalmem()/1024/1024/1024)}GB`,
|
|
||||||
// gulp --projects (angular2)
|
|
||||||
cd13: minimist(process.argv.slice(2)).projects
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function getTypeScriptVersion() {
|
|
||||||
try {
|
|
||||||
return require('typescript').version;
|
|
||||||
} catch (e) {
|
|
||||||
return 'unknown';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getNpmVersion() {
|
|
||||||
try {
|
|
||||||
return execSync('npm -v').toString().replace(/\s/, '');
|
|
||||||
} catch (e) {
|
|
||||||
return 'unknown';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getDartVersion() {
|
|
||||||
try {
|
|
||||||
return execSync('dart --version 2>&1').toString().replace(/.+: (\S+) [\s\S]+/, '$1')
|
|
||||||
} catch (e) {
|
|
||||||
return 'unknown';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function recordEvent(eventType, actionCategory, actionName, duration, label) {
|
|
||||||
// if universal-analytics is not yet installed, don't bother doing anything (e.g. when tracking initial npm install)
|
|
||||||
// build-analytics will however store the starting timestamp, so at least we can record the success/error event with duration
|
|
||||||
if (!ua) return;
|
|
||||||
|
|
||||||
if (duration) {
|
|
||||||
duration = Math.round(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (eventType) {
|
|
||||||
case 'start':
|
|
||||||
visitor.
|
|
||||||
event(actionCategory, actionName + ' (start)', label, null, customParams).
|
|
||||||
send();
|
|
||||||
break;
|
|
||||||
case 'success':
|
|
||||||
visitor.
|
|
||||||
event(actionCategory, actionName, label, duration, customParams).
|
|
||||||
timing(actionCategory, actionName, duration, label, customParams).
|
|
||||||
send();
|
|
||||||
break;
|
|
||||||
case 'error':
|
|
||||||
visitor.
|
|
||||||
event(actionCategory, actionName + ' (errored)', label, duration, customParams).
|
|
||||||
timing(actionCategory, actionName, duration, label, customParams).
|
|
||||||
send();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`unknown event type "${eventType}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
installStart: (actionName) => {
|
|
||||||
recordEvent('start', 'install', actionName);
|
|
||||||
},
|
|
||||||
|
|
||||||
installSuccess: (actionName, duration) => {
|
|
||||||
recordEvent('success', 'install', actionName, duration);
|
|
||||||
},
|
|
||||||
|
|
||||||
installError: (actionName, duration) => {
|
|
||||||
recordEvent('error', 'install', actionName, duration);
|
|
||||||
},
|
|
||||||
|
|
||||||
buildStart: (actionName) => {
|
|
||||||
recordEvent('start', 'build', actionName);
|
|
||||||
},
|
|
||||||
|
|
||||||
buildSuccess: (actionName, duration) => {
|
|
||||||
recordEvent('success', 'build', actionName, duration);
|
|
||||||
},
|
|
||||||
|
|
||||||
buildError: (actionName, duration) => {
|
|
||||||
recordEvent('error', 'build', actionName, duration);
|
|
||||||
},
|
|
||||||
|
|
||||||
ciStart: (actionName) => {
|
|
||||||
recordEvent('start', 'ci', actionName);
|
|
||||||
},
|
|
||||||
|
|
||||||
ciSuccess: (actionName, duration) => {
|
|
||||||
recordEvent('success', 'ci', actionName, duration);
|
|
||||||
},
|
|
||||||
|
|
||||||
ciError: (actionName, duration) => {
|
|
||||||
recordEvent('error', 'ci', actionName, duration);
|
|
||||||
},
|
|
||||||
|
|
||||||
bundleSize: (filePath, sizeInBytes, compressionLevel) => {
|
|
||||||
recordEvent('success', 'payload', compressionLevel, sizeInBytes, filePath);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,65 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// build analytics start|success|error|<exitCode> <actionCategory> <actionName>
|
|
||||||
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
|
|
||||||
var analytics = require('./analytics');
|
|
||||||
var fs = require('fs');
|
|
||||||
var path = require('path');
|
|
||||||
|
|
||||||
var eventType = process.argv[2];
|
|
||||||
var actionCategory = process.argv[3];
|
|
||||||
var actionName = process.argv[4];
|
|
||||||
|
|
||||||
|
|
||||||
if (!analytics[actionCategory + 'Start']) {
|
|
||||||
throw new Error('Unknown build-analytics actionCategory "' + actionCategory + '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
var exitCode = Number.parseInt(eventType, 10);
|
|
||||||
if (!Number.isNaN(exitCode)) {
|
|
||||||
eventType = (exitCode === 0) ? 'success' : 'error';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventType != 'start' && eventType != 'success' && eventType != 'error') {
|
|
||||||
throw new Error('Unknown build-analytics eventType "' + eventType + '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var startTimestampFilePath = path.resolve(path.join(__dirname, '..', '..', 'tmp', 'analytics', actionCategory + '-' + actionName));
|
|
||||||
var analyticsDirPath = path.dirname(startTimestampFilePath);
|
|
||||||
var tmpDirPath = path.dirname(analyticsDirPath);
|
|
||||||
|
|
||||||
|
|
||||||
if (!fs.existsSync(tmpDirPath)) {
|
|
||||||
fs.mkdirSync(tmpDirPath);
|
|
||||||
}
|
|
||||||
if (!fs.existsSync(analyticsDirPath)) {
|
|
||||||
fs.mkdirSync(analyticsDirPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
switch (eventType) {
|
|
||||||
case 'start':
|
|
||||||
analytics[actionCategory + 'Start'](actionName);
|
|
||||||
fs.writeFileSync(startTimestampFilePath, Date.now(), 'utf-8');
|
|
||||||
break;
|
|
||||||
case 'success':
|
|
||||||
try {
|
|
||||||
var startTime = fs.readFileSync(startTimestampFilePath, 'utf-8');
|
|
||||||
analytics[actionCategory + 'Success'](actionName, Date.now() - startTime);
|
|
||||||
fs.unlinkSync(startTimestampFilePath);
|
|
||||||
} catch(e) {
|
|
||||||
console.log('No start timestamp file "' + startTimestampFilePath + '" found, skipping analytics.');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'error':
|
|
||||||
var startTime = fs.readFileSync(startTimestampFilePath, 'utf-8');
|
|
||||||
analytics[actionCategory + 'Error'](actionName, Date.now() - startTime);
|
|
||||||
fs.unlinkSync(startTimestampFilePath);
|
|
||||||
}
|
|
|
@ -1,139 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let analytics = require('./analytics');
|
|
||||||
let gulp = require('gulp');
|
|
||||||
let gzip = require('gulp-gzip');
|
|
||||||
let merge2 = require('merge2');
|
|
||||||
let path = require('path');
|
|
||||||
let Stream = require('stream');
|
|
||||||
|
|
||||||
// Keys are a text description of compressionLevel.
|
|
||||||
// Values are "gzipOptions" passed to gzip.
|
|
||||||
// We report one `analytics.bundleSize` event
|
|
||||||
// * each file in `glob`
|
|
||||||
// * each entry in `_gzipConfigs`.
|
|
||||||
const _gzipConfigs = {
|
|
||||||
'uncompressed': null,
|
|
||||||
'gzip level=1': {level: 1}, // code.angular.js
|
|
||||||
'gzip level=2': {level: 2}, // github pages, most common
|
|
||||||
'gzip level=6': {level: 6}, // default gzip level
|
|
||||||
'gzip level=9': {level: 9} // max gzip level
|
|
||||||
};
|
|
||||||
|
|
||||||
const _defaultOptions = {
|
|
||||||
// @type {Object<string, number>}
|
|
||||||
// - Key(s) must match one of `_gzipConfigs` keys.
|
|
||||||
// - Values are the max size (in bytes) allowed for that configuration.
|
|
||||||
failConditions: {},
|
|
||||||
prefix: '',
|
|
||||||
// @type {Array<string>|boolean}
|
|
||||||
// Entries must match one of `_gzipConfigs` keys. These values will be
|
|
||||||
// printed to the screen.
|
|
||||||
// If this is the boolean value `true`, will print all to screen.
|
|
||||||
printToConsole: ['gzip level=6'],
|
|
||||||
reportAnalytics: true
|
|
||||||
};
|
|
||||||
|
|
||||||
// `glob` is a string representing a glob of files.
|
|
||||||
// options is an object containing zero or more of
|
|
||||||
// - printToConsole: Write debug to console. Default: false.
|
|
||||||
// - reportAnalytics: Report to Google Analytics. Default: true.
|
|
||||||
function reportSize(glob, options) {
|
|
||||||
options = options || {};
|
|
||||||
for (const key in _defaultOptions) {
|
|
||||||
if (!options.hasOwnProperty(key)) {
|
|
||||||
options[key] = _defaultOptions[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var errStream = _checkConfig(options);
|
|
||||||
if (errStream) {
|
|
||||||
return errStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
const allStreams = [];
|
|
||||||
for (const compressionLevel in _gzipConfigs) {
|
|
||||||
if (_gzipConfigs.hasOwnProperty(compressionLevel)) {
|
|
||||||
let stream = gulp.src(glob);
|
|
||||||
if (_gzipConfigs[compressionLevel]) {
|
|
||||||
stream = stream.pipe(gzip({gzipOptions: _gzipConfigs[compressionLevel]}));
|
|
||||||
}
|
|
||||||
allStreams.push(stream.on('data', checkFileSizeFactory(compressionLevel)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let didRun = false;
|
|
||||||
var errs = [];
|
|
||||||
return merge2(allStreams, {end: false})
|
|
||||||
.on('queueDrain', function() {
|
|
||||||
if (errs.length) {
|
|
||||||
errs.unshift(`Failed with ${errs.length} error(s).`);
|
|
||||||
this.emit('error', new Error(errs.join('\n ')));
|
|
||||||
}
|
|
||||||
if (!didRun) {
|
|
||||||
this.emit('error', new Error(`No file found for pattern "${glob}".`));
|
|
||||||
}
|
|
||||||
this.emit('end');
|
|
||||||
});
|
|
||||||
|
|
||||||
function checkFileSizeFactory(compressionLevel) {
|
|
||||||
return function checkFileSize(file) {
|
|
||||||
if (file.isNull()) return;
|
|
||||||
didRun = true;
|
|
||||||
var filePath = path.basename(file.path).replace('\.gz', '');
|
|
||||||
if (options.prefix) {
|
|
||||||
filePath = path.join(options.prefix, filePath);
|
|
||||||
}
|
|
||||||
const fileLen = file.contents.length;
|
|
||||||
if (options.reportAnalytics) {
|
|
||||||
analytics.bundleSize(filePath, fileLen, compressionLevel);
|
|
||||||
}
|
|
||||||
if (_shouldPrint(options, compressionLevel)) {
|
|
||||||
console.log(` ${filePath} => ${fileLen} bytes (${compressionLevel})`)
|
|
||||||
}
|
|
||||||
if (options.failConditions.hasOwnProperty(compressionLevel)) {
|
|
||||||
if (options.failConditions[compressionLevel] < fileLen) {
|
|
||||||
errs.push(`Max size for "${compressionLevel}" is ` +
|
|
||||||
`${options.failConditions[compressionLevel]}, but the size is now ${fileLen}.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _shouldPrint(options, compressionLevel) {
|
|
||||||
const printAll = typeof options.printToConsole == 'boolean' && options.printToConsole;
|
|
||||||
return printAll || options.printToConsole.indexOf(compressionLevel) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns an error stream if the fail conditions are not provided property.
|
|
||||||
// Returns `null` if everything is fine.
|
|
||||||
function _checkConfig(config) {
|
|
||||||
for (const key in config.failConditions) {
|
|
||||||
if (config.failConditions.hasOwnProperty(key)) {
|
|
||||||
if (!_gzipConfigs.hasOwnProperty(key)) {
|
|
||||||
var stream = new Stream();
|
|
||||||
stream.emit(
|
|
||||||
'error',
|
|
||||||
new Error(`failCondition for "${key}" will not be tested. Check _gzipConfigs.`));
|
|
||||||
stream.emit('end');
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof config.printToConsole != 'boolean') {
|
|
||||||
for (var i = 0; i < config.printToConsole.length; ++i) {
|
|
||||||
const key = config.printToConsole[i];
|
|
||||||
if (!_gzipConfigs.hasOwnProperty(key)) {
|
|
||||||
var stream = new Stream();
|
|
||||||
stream.emit(
|
|
||||||
'error',
|
|
||||||
new Error(`Incorrect value "${key}" in printToConsole. Check _gzipConfigs.`));
|
|
||||||
stream.emit('end');
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = reportSize;
|
|
|
@ -1,174 +0,0 @@
|
||||||
var broccoli = require('broccoli');
|
|
||||||
var fs = require('fs');
|
|
||||||
var makeBrowserTree = require('./trees/browser_tree');
|
|
||||||
var makeNodeTree = require('./trees/node_tree');
|
|
||||||
var path = require('path');
|
|
||||||
var printSlowTrees = require('broccoli-slow-trees');
|
|
||||||
var Q = require('q');
|
|
||||||
|
|
||||||
export type ProjectMap = {
|
|
||||||
[key: string]: boolean
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Options = {
|
|
||||||
projects: ProjectMap; noTypeChecks: boolean; generateEs6: boolean; useBundles: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface AngularBuilderOptions {
|
|
||||||
outputPath: string;
|
|
||||||
dartSDK?: any;
|
|
||||||
logs?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* BroccoliBuilder facade for all of our build pipelines.
|
|
||||||
*/
|
|
||||||
export class AngularBuilder {
|
|
||||||
private nodeBuilder: BroccoliBuilder;
|
|
||||||
private browserDevBuilder: BroccoliBuilder;
|
|
||||||
private browserProdBuilder: BroccoliBuilder;
|
|
||||||
private dartBuilder: BroccoliBuilder;
|
|
||||||
private outputPath: string;
|
|
||||||
private firstResult: BuildResult;
|
|
||||||
|
|
||||||
constructor(public options: AngularBuilderOptions) { this.outputPath = options.outputPath; }
|
|
||||||
|
|
||||||
|
|
||||||
public rebuildBrowserDevTree(opts: Options): Promise<BuildResult> {
|
|
||||||
this.browserDevBuilder = this.browserDevBuilder || this.makeBrowserDevBuilder(opts);
|
|
||||||
return this.rebuild(this.browserDevBuilder, 'js.dev');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public rebuildBrowserProdTree(opts: Options): Promise<BuildResult> {
|
|
||||||
this.browserProdBuilder = this.browserProdBuilder || this.makeBrowserProdBuilder(opts);
|
|
||||||
return this.rebuild(this.browserProdBuilder, 'js.prod');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public rebuildNodeTree(opts: Options): Promise<BuildResult> {
|
|
||||||
this.nodeBuilder = this.nodeBuilder || this.makeNodeBuilder(opts.projects);
|
|
||||||
return this.rebuild(this.nodeBuilder, 'js.cjs');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public rebuildDartTree(projects: ProjectMap): Promise<BuildResult> {
|
|
||||||
this.dartBuilder = this.dartBuilder || this.makeDartBuilder(projects);
|
|
||||||
return this.rebuild(this.dartBuilder, 'dart');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
cleanup(): Promise<any> {
|
|
||||||
return Q.all([
|
|
||||||
this.nodeBuilder && this.nodeBuilder.cleanup(),
|
|
||||||
this.browserDevBuilder && this.browserDevBuilder.cleanup(),
|
|
||||||
this.browserProdBuilder && this.browserProdBuilder.cleanup()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private makeBrowserDevBuilder(opts: Options): BroccoliBuilder {
|
|
||||||
let tree = makeBrowserTree(
|
|
||||||
{
|
|
||||||
name: 'dev',
|
|
||||||
typeAssertions: true,
|
|
||||||
sourceMaps: true,
|
|
||||||
projects: opts.projects,
|
|
||||||
noTypeChecks: opts.noTypeChecks,
|
|
||||||
generateEs6: opts.generateEs6,
|
|
||||||
useBundles: opts.useBundles
|
|
||||||
},
|
|
||||||
path.join(this.outputPath, 'js', 'dev'));
|
|
||||||
return new broccoli.Builder(tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private makeBrowserProdBuilder(opts: Options): BroccoliBuilder {
|
|
||||||
let tree = makeBrowserTree(
|
|
||||||
{
|
|
||||||
name: 'prod',
|
|
||||||
typeAssertions: false,
|
|
||||||
sourceMaps: false,
|
|
||||||
projects: opts.projects,
|
|
||||||
noTypeChecks: opts.noTypeChecks,
|
|
||||||
generateEs6: opts.generateEs6,
|
|
||||||
useBundles: opts.useBundles
|
|
||||||
},
|
|
||||||
path.join(this.outputPath, 'js', 'prod'));
|
|
||||||
return new broccoli.Builder(tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private makeNodeBuilder(projects: ProjectMap): BroccoliBuilder {
|
|
||||||
let tree = makeNodeTree(projects, path.join(this.outputPath, 'js', 'cjs'));
|
|
||||||
return new broccoli.Builder(tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private makeDartBuilder(projects: ProjectMap): BroccoliBuilder {
|
|
||||||
let options = {
|
|
||||||
outputPath: path.join(this.outputPath, 'dart'),
|
|
||||||
dartSDK: this.options.dartSDK,
|
|
||||||
logs: this.options.logs,
|
|
||||||
projects: projects
|
|
||||||
};
|
|
||||||
// Workaround for https://github.com/dart-lang/dart_style/issues/493
|
|
||||||
var makeDartTree = require('./trees/dart_tree');
|
|
||||||
let tree = makeDartTree(options);
|
|
||||||
return new broccoli.Builder(tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private rebuild(builder: BroccoliBuilder, name: string): Promise<BuildResult> {
|
|
||||||
return builder.build().then<BuildResult>(
|
|
||||||
(result) => {
|
|
||||||
if (!this.firstResult) {
|
|
||||||
this.firstResult = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
printSlowTrees(result.graph);
|
|
||||||
writeBuildLog(result, name);
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
(error):
|
|
||||||
any => {
|
|
||||||
// the build tree is the same during rebuilds, only leaf properties of the nodes
|
|
||||||
// change
|
|
||||||
// so let's traverse it and get updated values for input/cache/output paths
|
|
||||||
if (this.firstResult) {
|
|
||||||
writeBuildLog(this.firstResult, name);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function writeBuildLog(result: BuildResult, name: string) {
|
|
||||||
let logPath = `tmp/build.${name}.log`;
|
|
||||||
let prevLogPath = logPath + '.previous';
|
|
||||||
let formattedLogContent = JSON.stringify(broccoliNodeToBuildNode(result.graph), null, 2);
|
|
||||||
|
|
||||||
if (fs.existsSync(prevLogPath)) fs.unlinkSync(prevLogPath);
|
|
||||||
if (fs.existsSync(logPath)) fs.renameSync(logPath, prevLogPath);
|
|
||||||
fs.writeFileSync(logPath, formattedLogContent, {encoding: 'utf-8'});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function broccoliNodeToBuildNode(broccoliNode: BroccoliNode): BuildNode {
|
|
||||||
let tree = broccoliNode.tree.newStyleTree || broccoliNode.tree;
|
|
||||||
|
|
||||||
return new BuildNode(
|
|
||||||
tree.description || (<any>tree.constructor).name,
|
|
||||||
tree.inputPath ? [tree.inputPath] : tree.inputPaths, tree.cachePath, tree.outputPath,
|
|
||||||
broccoliNode.selfTime / (1000 * 1000 * 1000), broccoliNode.totalTime / (1000 * 1000 * 1000),
|
|
||||||
broccoliNode.subtrees.map(broccoliNodeToBuildNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class BuildNode {
|
|
||||||
constructor(
|
|
||||||
public pluginName: string, public inputPaths: string[], public cachePath: string,
|
|
||||||
public outputPath: string, public selfTime: number, public totalTime: number,
|
|
||||||
public inputNodes: BuildNode[]) {}
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
import fs = require('fs');
|
|
||||||
import fse = require('fs-extra');
|
|
||||||
import path = require('path');
|
|
||||||
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks that modules do not import files that are not supposed to import.
|
|
||||||
*
|
|
||||||
* This guarantees that platform-independent modules remain platoform-independent.
|
|
||||||
*/
|
|
||||||
class CheckImports implements DiffingBroccoliPlugin {
|
|
||||||
static IMPORT_DECL_REGEXP = new RegExp(`^import[^;]+;`, 'mg');
|
|
||||||
static IMPORT_PATH_REGEXP = new RegExp(`['"]([^'"]+)+['"]`, 'm');
|
|
||||||
|
|
||||||
static ALLOWED_IMPORTS: {[s: string]: string[]} = {
|
|
||||||
'angular2/src/core': ['angular2/src/facade'],
|
|
||||||
'angular2/src/facade': ['rxjs'],
|
|
||||||
'angular2/src/common': ['angular2/core', 'angular2/src/facade'],
|
|
||||||
'angular2/src/http': ['angular2/core', 'angular2/src/facade', 'rxjs'],
|
|
||||||
'angular2/src/upgrade':
|
|
||||||
['angular2/core', 'angular2/src/facade', 'angular2/platform/browser', 'angular2/compiler']
|
|
||||||
//"angular2/src/render": [
|
|
||||||
// "angular2/animate",
|
|
||||||
// "angular2/core",
|
|
||||||
// "angular2/src/facade",
|
|
||||||
//]
|
|
||||||
};
|
|
||||||
|
|
||||||
private initRun = true;
|
|
||||||
|
|
||||||
constructor(private inputPath: string, private cachePath: string, private options: number) {}
|
|
||||||
|
|
||||||
rebuild(treeDiff: DiffResult) {
|
|
||||||
const errors = this.checkAllPaths(treeDiff);
|
|
||||||
if (errors.length > 0) {
|
|
||||||
throw new Error(
|
|
||||||
`The following imports are not allowed because they break barrel boundaries:\n${errors.join("\n")}`);
|
|
||||||
}
|
|
||||||
this.symlinkInputAndCacheIfNeeded();
|
|
||||||
return treeDiff;
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkAllPaths(treeDiff: DiffResult) {
|
|
||||||
const changesFiles = treeDiff.addedPaths.concat(treeDiff.changedPaths);
|
|
||||||
return flatMap(changesFiles, _ => this.checkFilePath(_));
|
|
||||||
}
|
|
||||||
|
|
||||||
private symlinkInputAndCacheIfNeeded() {
|
|
||||||
if (this.initRun) {
|
|
||||||
fs.rmdirSync(this.cachePath);
|
|
||||||
fs.symlinkSync(this.inputPath, this.cachePath);
|
|
||||||
}
|
|
||||||
this.initRun = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkFilePath(filePath: string) {
|
|
||||||
const sourceFilePath = path.join(this.inputPath, filePath);
|
|
||||||
if (endsWith(sourceFilePath, '.ts') && fs.existsSync(sourceFilePath)) {
|
|
||||||
const content = fs.readFileSync(sourceFilePath, 'UTF-8');
|
|
||||||
const imports = content.match(CheckImports.IMPORT_DECL_REGEXP);
|
|
||||||
if (imports) {
|
|
||||||
return imports.filter(i => !this.isAllowedImport(filePath, i))
|
|
||||||
.map(i => this.formatError(filePath, i));
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
private isAllowedImport(sourceFile: string, importDecl: string): boolean {
|
|
||||||
const res = CheckImports.IMPORT_PATH_REGEXP.exec(importDecl);
|
|
||||||
if (!res || res.length < 2) return true; // non-es6 import
|
|
||||||
const importPath = res[1];
|
|
||||||
|
|
||||||
if (startsWith(importPath, './') || startsWith(importPath, '../')) return true;
|
|
||||||
|
|
||||||
const c = CheckImports.ALLOWED_IMPORTS;
|
|
||||||
for (var prop in c) {
|
|
||||||
if (c.hasOwnProperty(prop) && startsWith(sourceFile, prop)) {
|
|
||||||
const allowedPaths = c[prop];
|
|
||||||
return startsWith(importPath, prop) ||
|
|
||||||
allowedPaths.filter(p => startsWith(importPath, p)).length > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private formatError(filePath: string, importPath: string): string {
|
|
||||||
const i = importPath.replace(new RegExp(`\n`, 'g'), '\\n');
|
|
||||||
return `${filePath}: ${i}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function startsWith(str: string, substring: string): boolean {
|
|
||||||
return str.substring(0, substring.length) === substring;
|
|
||||||
}
|
|
||||||
|
|
||||||
function endsWith(str: string, substring: string): boolean {
|
|
||||||
return str.indexOf(substring, str.length - substring.length) !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function flatMap<T, U>(arr: T[], fn: (t: T) => U[]): U[] {
|
|
||||||
return [].concat(...arr.map(fn));
|
|
||||||
}
|
|
||||||
|
|
||||||
export default wrapDiffingPlugin(CheckImports);
|
|
|
@ -1,106 +0,0 @@
|
||||||
import fse = require('fs-extra');
|
|
||||||
import path = require('path');
|
|
||||||
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
|
|
||||||
import {AngularBuilderOptions} from './angular_builder';
|
|
||||||
|
|
||||||
var spawn = require('child_process').spawn;
|
|
||||||
var exec = require('child_process').exec;
|
|
||||||
|
|
||||||
function processToPromise(process: NodeJS.Process) {
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
process.on('close', function(code: number) {
|
|
||||||
if (code) {
|
|
||||||
reject(code);
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class DartFormatter implements DiffingBroccoliPlugin {
|
|
||||||
private DARTFMT: string;
|
|
||||||
private verbose: boolean;
|
|
||||||
private firstBuild: boolean = true;
|
|
||||||
|
|
||||||
constructor(public inputPath: string, public cachePath: string, options: AngularBuilderOptions) {
|
|
||||||
if (!options.dartSDK) throw new Error('Missing Dart SDK');
|
|
||||||
this.DARTFMT = options.dartSDK.DARTFMT;
|
|
||||||
this.verbose = options.logs.dartfmt;
|
|
||||||
}
|
|
||||||
|
|
||||||
rebuild(treeDiff: DiffResult): Promise<any> {
|
|
||||||
let args = ['-w'];
|
|
||||||
let argsLength = 2;
|
|
||||||
let argPackages: string[][] = [];
|
|
||||||
let firstBuild = this.firstBuild;
|
|
||||||
treeDiff.addedPaths.concat(treeDiff.changedPaths).forEach((changedFile) => {
|
|
||||||
let sourcePath = path.join(this.inputPath, changedFile);
|
|
||||||
let destPath = path.join(this.cachePath, changedFile);
|
|
||||||
if (!firstBuild && /\.dart$/.test(changedFile)) {
|
|
||||||
if ((argsLength + destPath.length + 2) >= 0x2000) {
|
|
||||||
// Win32 command line arguments length
|
|
||||||
argPackages.push(args);
|
|
||||||
args = ['-w'];
|
|
||||||
argsLength = 2;
|
|
||||||
}
|
|
||||||
args.push(destPath);
|
|
||||||
argsLength += destPath.length + 2;
|
|
||||||
}
|
|
||||||
fse.copySync(sourcePath, destPath);
|
|
||||||
});
|
|
||||||
treeDiff.removedPaths.forEach((removedFile) => {
|
|
||||||
let destPath = path.join(this.cachePath, removedFile);
|
|
||||||
fse.removeSync(destPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!firstBuild && args.length > 1) {
|
|
||||||
argPackages.push(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
let execute = (args: string[]) => {
|
|
||||||
if (args.length < 2) return Promise.resolve();
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
|
||||||
exec(this.DARTFMT + ' ' + args.join(' '), (err: Error, stdout: string, stderr: string) => {
|
|
||||||
if (this.verbose) {
|
|
||||||
console.log(stdout);
|
|
||||||
}
|
|
||||||
if (err) {
|
|
||||||
console.error(shortenFormatterOutput(stderr));
|
|
||||||
reject('Formatting failed.');
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (firstBuild) {
|
|
||||||
// On firstBuild, format the entire cachePath
|
|
||||||
this.firstBuild = false;
|
|
||||||
return execute(['-w', this.cachePath]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(argPackages.map(execute));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default wrapDiffingPlugin(DartFormatter);
|
|
||||||
|
|
||||||
var ARROW_LINE = /^(\s+)\^+/;
|
|
||||||
var BEFORE_CHARS = 15;
|
|
||||||
var stripAnsi = require('strip-ansi');
|
|
||||||
function shortenFormatterOutput(formatterOutput: string) {
|
|
||||||
var lines = formatterOutput.split('\n');
|
|
||||||
var match: string, line: string;
|
|
||||||
for (var i = 0; i < lines.length; i += 1) {
|
|
||||||
line = lines[i];
|
|
||||||
if (match = stripAnsi(line).match(ARROW_LINE)) {
|
|
||||||
let leadingWhitespace = match[1].length;
|
|
||||||
let leadingCodeChars = Math.min(leadingWhitespace, BEFORE_CHARS);
|
|
||||||
lines[i] = line.substr(leadingWhitespace - leadingCodeChars);
|
|
||||||
lines[i - 1] = lines[i - 1].substr(leadingWhitespace - leadingCodeChars, 80) + '…';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lines.join('\n');
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
import fs = require('fs');
|
|
||||||
import fse = require('fs-extra');
|
|
||||||
import path = require('path');
|
|
||||||
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intercepts each file as it is copied to the destination tempdir,
|
|
||||||
* and tees a copy to the given path outside the tmp dir.
|
|
||||||
*/
|
|
||||||
class DestCopy implements DiffingBroccoliPlugin {
|
|
||||||
constructor(private inputPath: string, private cachePath: string, private outputRoot: string) {}
|
|
||||||
|
|
||||||
|
|
||||||
rebuild(treeDiff: DiffResult) {
|
|
||||||
treeDiff.addedPaths.concat(treeDiff.changedPaths).forEach((changedFilePath) => {
|
|
||||||
var destFilePath = path.join(this.outputRoot, changedFilePath);
|
|
||||||
|
|
||||||
var destDirPath = path.dirname(destFilePath);
|
|
||||||
fse.mkdirsSync(destDirPath);
|
|
||||||
fse.copySync(path.join(this.inputPath, changedFilePath), destFilePath);
|
|
||||||
});
|
|
||||||
|
|
||||||
treeDiff.removedPaths.forEach((removedFilePath) => {
|
|
||||||
var destFilePath = path.join(this.outputRoot, removedFilePath);
|
|
||||||
|
|
||||||
// TODO: what about obsolete directories? we are not cleaning those up yet
|
|
||||||
fs.unlinkSync(destFilePath);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default wrapDiffingPlugin(DestCopy);
|
|
|
@ -1,12 +0,0 @@
|
||||||
interface FilterOptions {
|
|
||||||
extensions?: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
declare class Filter {
|
|
||||||
constructor(inputTree: any, options?: FilterOptions);
|
|
||||||
processString(contents: string, relativePath: string): string;
|
|
||||||
// NB: This function is probably not intended as part of the public API
|
|
||||||
processFile(srcDir: string, destDir: string, relativePath: string): Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export = Filter;
|
|
|
@ -1,74 +0,0 @@
|
||||||
let mockfs = require('mock-fs');
|
|
||||||
import fs = require('fs');
|
|
||||||
import path = require('path');
|
|
||||||
import {TreeDiffer} from './tree-differ';
|
|
||||||
import {DiffingFlatten} from './broccoli-flatten';
|
|
||||||
|
|
||||||
describe('Flatten', () => {
|
|
||||||
afterEach(() => mockfs.restore());
|
|
||||||
|
|
||||||
let flatten = (inputPaths: string) => new DiffingFlatten(inputPaths, 'output', null);
|
|
||||||
let read = (path: string) => fs.readFileSync(path, {encoding: 'utf-8'});
|
|
||||||
let rm = (path: string) => fs.unlinkSync(path);
|
|
||||||
let write =
|
|
||||||
(path: string, content: string) => { fs.writeFileSync(path, content, {encoding: 'utf-8'}); }
|
|
||||||
|
|
||||||
|
|
||||||
it('should flatten files and be incremental', () => {
|
|
||||||
let testDir = {
|
|
||||||
'input': {
|
|
||||||
'dir1': {
|
|
||||||
'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)}),
|
|
||||||
'file-2.txt': mockfs.file({content: 'file-2.txt content', mtime: new Date(1000)}),
|
|
||||||
'subdir-1': {
|
|
||||||
'file-1.1.txt': mockfs.file({content: 'file-1.1.txt content', mtime: new Date(1000)})
|
|
||||||
},
|
|
||||||
'empty-dir': {}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'output': {}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let differ = new TreeDiffer('testLabel', 'input');
|
|
||||||
let flattenedTree = flatten('input');
|
|
||||||
flattenedTree.rebuild(differ.diffTree());
|
|
||||||
|
|
||||||
expect(fs.readdirSync('output')).toEqual(['file-1.1.txt', 'file-1.txt', 'file-2.txt']);
|
|
||||||
// fails due to a mock-fs bug related to reading symlinks?
|
|
||||||
// expect(read('output/file-1.1.txt')).toBe('file-1.1.txt content');
|
|
||||||
|
|
||||||
|
|
||||||
// delete a file
|
|
||||||
rm('input/dir1/file-1.txt');
|
|
||||||
// add a new one
|
|
||||||
write('input/dir1/file-3.txt', 'file-3.txt content');
|
|
||||||
|
|
||||||
flattenedTree.rebuild(differ.diffTree());
|
|
||||||
|
|
||||||
expect(fs.readdirSync('output')).toEqual(['file-1.1.txt', 'file-2.txt', 'file-3.txt']);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should throw an exception if duplicates are found', () => {
|
|
||||||
let testDir = {
|
|
||||||
'input': {
|
|
||||||
'dir1': {
|
|
||||||
'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)}),
|
|
||||||
'subdir-1':
|
|
||||||
{'file-1.txt': mockfs.file({content: 'file-1.1.txt content', mtime: new Date(1000)})},
|
|
||||||
'empty-dir': {}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'output': {}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let differ = new TreeDiffer('testLabel', 'input');
|
|
||||||
let flattenedTree = flatten('input');
|
|
||||||
expect(() => flattenedTree.rebuild(differ.diffTree()))
|
|
||||||
.toThrowError(
|
|
||||||
'Duplicate file \'file-1.txt\' found in path \'dir1' + path.sep + 'subdir-1' +
|
|
||||||
path.sep + 'file-1.txt\'');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,56 +0,0 @@
|
||||||
import fs = require('fs');
|
|
||||||
import fse = require('fs-extra');
|
|
||||||
import path = require('path');
|
|
||||||
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
|
|
||||||
import {AngularBuilderOptions} from './angular_builder';
|
|
||||||
|
|
||||||
var symlinkOrCopy = require('symlink-or-copy').sync;
|
|
||||||
|
|
||||||
var isWindows = process.platform === 'win32';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intercepts each changed file and replaces its contents with
|
|
||||||
* the associated changes.
|
|
||||||
*/
|
|
||||||
export class DiffingFlatten implements DiffingBroccoliPlugin {
|
|
||||||
constructor(
|
|
||||||
private inputPath: string, private cachePath: string,
|
|
||||||
private options: AngularBuilderOptions) {}
|
|
||||||
|
|
||||||
|
|
||||||
rebuild(treeDiff: DiffResult) {
|
|
||||||
let pathsToUpdate = treeDiff.addedPaths;
|
|
||||||
|
|
||||||
// since we need to run on Windows as well we can't rely on symlinks being available,
|
|
||||||
// which means that we need to respond to both added and changed paths
|
|
||||||
if (isWindows) {
|
|
||||||
pathsToUpdate = pathsToUpdate.concat(treeDiff.changedPaths);
|
|
||||||
}
|
|
||||||
|
|
||||||
pathsToUpdate.forEach((changedFilePath) => {
|
|
||||||
var sourceFilePath = path.join(this.inputPath, changedFilePath);
|
|
||||||
var destFilePath = path.join(this.cachePath, path.basename(changedFilePath));
|
|
||||||
var destDirPath = path.dirname(destFilePath);
|
|
||||||
|
|
||||||
if (!fs.existsSync(destDirPath)) {
|
|
||||||
fse.mkdirpSync(destDirPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(destFilePath)) {
|
|
||||||
symlinkOrCopy(sourceFilePath, destFilePath);
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
`Duplicate file '${path.basename(changedFilePath)}' ` +
|
|
||||||
`found in path '${changedFilePath}'`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
treeDiff.removedPaths.forEach((removedFilePath) => {
|
|
||||||
var destFilePath = path.join(this.cachePath, path.basename(removedFilePath));
|
|
||||||
fs.unlinkSync(destFilePath);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default wrapDiffingPlugin(DiffingFlatten);
|
|
|
@ -1,86 +0,0 @@
|
||||||
import fs = require('fs');
|
|
||||||
import fse = require('fs-extra');
|
|
||||||
import path = require('path');
|
|
||||||
import childProcess = require('child_process');
|
|
||||||
var glob = require('glob');
|
|
||||||
|
|
||||||
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intercepts each changed file and replaces its contents with
|
|
||||||
* the output of the generator.
|
|
||||||
*/
|
|
||||||
class GeneratorForTest implements DiffingBroccoliPlugin {
|
|
||||||
private seenFiles: {[key: string]: boolean} = {};
|
|
||||||
|
|
||||||
constructor(private inputPath: string, private outputPath: string, private options: {
|
|
||||||
files: string[],
|
|
||||||
dartPath: string
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
rebuild(treeDiff: DiffResult) {
|
|
||||||
var matchedFiles: string[] = [];
|
|
||||||
this.options.files.forEach(
|
|
||||||
(file) => { matchedFiles = matchedFiles.concat(glob.sync(file, {cwd: this.inputPath})); });
|
|
||||||
return Promise
|
|
||||||
.all(matchedFiles.map((matchedFile) => {
|
|
||||||
var inputFilePath = path.join(this.inputPath, matchedFile);
|
|
||||||
var outputFilePath = path.join(this.outputPath, matchedFile);
|
|
||||||
|
|
||||||
var outputDirPath = path.dirname(outputFilePath);
|
|
||||||
if (!fs.existsSync(outputDirPath)) {
|
|
||||||
fse.mkdirpSync(outputDirPath);
|
|
||||||
}
|
|
||||||
return this.invokeGenerator(matchedFile, inputFilePath, outputFilePath)
|
|
||||||
}))
|
|
||||||
.then(() => {
|
|
||||||
var result = new DiffResult();
|
|
||||||
matchedFiles.forEach((file) => {
|
|
||||||
if (!this.seenFiles[file]) {
|
|
||||||
result.addedPaths.push(file);
|
|
||||||
this.seenFiles[file] = true;
|
|
||||||
} else {
|
|
||||||
result.changedPaths.push(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private invokeGenerator(file: string, inputFilePath: string, outputFilePath: string):
|
|
||||||
Promise<any> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
var args: string[];
|
|
||||||
var vmPath: string;
|
|
||||||
var env: {[key: string]: string};
|
|
||||||
if (this.options.dartPath) {
|
|
||||||
vmPath = this.options.dartPath;
|
|
||||||
args = [`--package-root=${this.inputPath}`, '--checked', inputFilePath, file];
|
|
||||||
env = {};
|
|
||||||
} else {
|
|
||||||
vmPath = process.execPath;
|
|
||||||
var script = `require('reflect-metadata');require('${inputFilePath}').main(['${file}']);`;
|
|
||||||
args = ['-e', script];
|
|
||||||
env = {'NODE_PATH': this.inputPath};
|
|
||||||
}
|
|
||||||
|
|
||||||
var stdoutStream = fs.createWriteStream(outputFilePath);
|
|
||||||
var proc = childProcess.spawn(vmPath, args, {
|
|
||||||
stdio: ['ignore', 'pipe', 'inherit'],
|
|
||||||
env: (<any>Object)['assign']({}, process.env, env)
|
|
||||||
});
|
|
||||||
proc.on('error', function(code: any) {
|
|
||||||
console.error(code);
|
|
||||||
reject(new Error(
|
|
||||||
'Failed while generating code. Please run manually: ' + vmPath + ' ' + args.join(' ')));
|
|
||||||
});
|
|
||||||
proc.on('close', function() {
|
|
||||||
stdoutStream.close();
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
proc.stdout.pipe(stdoutStream);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default wrapDiffingPlugin(GeneratorForTest);
|
|
|
@ -1,50 +0,0 @@
|
||||||
import fs = require('fs');
|
|
||||||
import fse = require('fs-extra');
|
|
||||||
import path = require('path');
|
|
||||||
var _ = require('lodash');
|
|
||||||
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
|
|
||||||
|
|
||||||
export interface LodashRendererOptions {
|
|
||||||
encoding?: string;
|
|
||||||
context?: any;
|
|
||||||
// files option unsupported --- use Funnel on inputTree instead.
|
|
||||||
files?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const kDefaultOptions: LodashRendererOptions = {
|
|
||||||
encoding: 'utf-8',
|
|
||||||
context: {},
|
|
||||||
files: []
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intercepts each changed file and replaces its contents with
|
|
||||||
* the associated changes.
|
|
||||||
*/
|
|
||||||
export class LodashRenderer implements DiffingBroccoliPlugin {
|
|
||||||
constructor(
|
|
||||||
private inputPath: string, private cachePath: string,
|
|
||||||
private options: LodashRendererOptions = kDefaultOptions) {}
|
|
||||||
|
|
||||||
rebuild(treeDiff: DiffResult) {
|
|
||||||
let {encoding = 'utf-8', context = {}} = this.options;
|
|
||||||
let processFile = (relativePath: string) => {
|
|
||||||
let sourceFilePath = path.join(this.inputPath, relativePath);
|
|
||||||
let destFilePath = path.join(this.cachePath, relativePath);
|
|
||||||
let content = fs.readFileSync(sourceFilePath, {encoding});
|
|
||||||
let transformedContent = _.template(content)(context);
|
|
||||||
fse.outputFileSync(destFilePath, transformedContent);
|
|
||||||
};
|
|
||||||
|
|
||||||
let removeFile = (relativePath: string) => {
|
|
||||||
let destFilePath = path.join(this.cachePath, relativePath);
|
|
||||||
fs.unlinkSync(destFilePath);
|
|
||||||
};
|
|
||||||
|
|
||||||
treeDiff.addedPaths.concat(treeDiff.changedPaths).forEach(processFile);
|
|
||||||
treeDiff.removedPaths.forEach(removeFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default wrapDiffingPlugin(LodashRenderer);
|
|
|
@ -1,87 +0,0 @@
|
||||||
let mockfs = require('mock-fs');
|
|
||||||
import fs = require('fs');
|
|
||||||
import {TreeDiffer, DiffResult} from './tree-differ';
|
|
||||||
import {MergeTrees} from './broccoli-merge-trees';
|
|
||||||
|
|
||||||
describe('MergeTrees', () => {
|
|
||||||
afterEach(() => mockfs.restore());
|
|
||||||
|
|
||||||
function mergeTrees(inputPaths: string[], cachePath: string, options: {}) {
|
|
||||||
return new MergeTrees(inputPaths, cachePath, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
function MakeTreeDiffers(rootDirs: string[]): TreeDiffer[] {
|
|
||||||
return rootDirs.map((rootDir) => new TreeDiffer('MergeTrees', rootDir));
|
|
||||||
}
|
|
||||||
|
|
||||||
let diffTrees = (differs: TreeDiffer[]): DiffResult[] => differs.map(tree => tree.diffTree());
|
|
||||||
function read(path: string) { return fs.readFileSync(path, 'utf-8'); }
|
|
||||||
|
|
||||||
it('should copy the file from the right-most inputTree with overwrite=true', () => {
|
|
||||||
let testDir: any = {
|
|
||||||
'tree1': {'foo.js': mockfs.file({content: 'tree1/foo.js content', mtime: new Date(1000)})},
|
|
||||||
'tree2': {'foo.js': mockfs.file({content: 'tree2/foo.js content', mtime: new Date(1000)})},
|
|
||||||
'tree3': {'foo.js': mockfs.file({content: 'tree3/foo.js content', mtime: new Date(1000)})}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
let treeDiffer = MakeTreeDiffers(['tree1', 'tree2', 'tree3']);
|
|
||||||
let treeMerger = mergeTrees(['tree1', 'tree2', 'tree3'], 'dest', {overwrite: true});
|
|
||||||
treeMerger.rebuild(diffTrees(treeDiffer));
|
|
||||||
expect(read('dest/foo.js')).toBe('tree3/foo.js content');
|
|
||||||
|
|
||||||
delete testDir.tree2['foo.js'];
|
|
||||||
delete testDir.tree3['foo.js'];
|
|
||||||
mockfs(testDir);
|
|
||||||
treeMerger.rebuild(diffTrees(treeDiffer));
|
|
||||||
expect(read('dest/foo.js')).toBe('tree1/foo.js content');
|
|
||||||
|
|
||||||
testDir.tree2['foo.js'] = mockfs.file({content: 'tree2/foo.js content', mtime: new Date(1000)});
|
|
||||||
mockfs(testDir);
|
|
||||||
treeMerger.rebuild(diffTrees(treeDiffer));
|
|
||||||
expect(read('dest/foo.js')).toBe('tree2/foo.js content');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if duplicates are found during the initial build', () => {
|
|
||||||
let testDir: any = {
|
|
||||||
'tree1': {'foo.js': mockfs.file({content: 'tree1/foo.js content', mtime: new Date(1000)})},
|
|
||||||
'tree2': {'foo.js': mockfs.file({content: 'tree2/foo.js content', mtime: new Date(1000)})},
|
|
||||||
'tree3': {'foo.js': mockfs.file({content: 'tree3/foo.js content', mtime: new Date(1000)})}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
let treeDiffer = MakeTreeDiffers(['tree1', 'tree2', 'tree3']);
|
|
||||||
let treeMerger = mergeTrees(['tree1', 'tree2', 'tree3'], 'dest', {});
|
|
||||||
expect(() => treeMerger.rebuild(diffTrees(treeDiffer)))
|
|
||||||
.toThrowError(
|
|
||||||
'Duplicate path found while merging trees. Path: "foo.js".\n' +
|
|
||||||
'Either remove the duplicate or enable the "overwrite" option for this merge.');
|
|
||||||
|
|
||||||
testDir = {
|
|
||||||
'tree1': {'foo.js': mockfs.file({content: 'tree1/foo.js content', mtime: new Date(1000)})},
|
|
||||||
'tree2': {},
|
|
||||||
'tree3': {}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should throw if duplicates are found during rebuild', () => {
|
|
||||||
let testDir: any = {
|
|
||||||
'tree1': {'foo.js': mockfs.file({content: 'tree1/foo.js content', mtime: new Date(1000)})},
|
|
||||||
'tree2': {},
|
|
||||||
'tree3': {}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let treeDiffer = MakeTreeDiffers(['tree1', 'tree2', 'tree3']);
|
|
||||||
let treeMerger = mergeTrees(['tree1', 'tree2', 'tree3'], 'dest', {});
|
|
||||||
expect(() => treeMerger.rebuild(diffTrees(treeDiffer))).not.toThrow();
|
|
||||||
|
|
||||||
|
|
||||||
testDir.tree2['foo.js'] = mockfs.file({content: 'tree2/foo.js content', mtime: new Date(1000)});
|
|
||||||
mockfs(testDir);
|
|
||||||
expect(() => treeMerger.rebuild(diffTrees(treeDiffer)))
|
|
||||||
.toThrowError(
|
|
||||||
'Duplicate path found while merging trees. Path: "foo.js".\n' +
|
|
||||||
'Either remove the duplicate or enable the "overwrite" option for this merge.');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,135 +0,0 @@
|
||||||
import fs = require('fs');
|
|
||||||
import fse = require('fs-extra');
|
|
||||||
import path = require('path');
|
|
||||||
var symlinkOrCopySync = require('symlink-or-copy').sync;
|
|
||||||
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
|
|
||||||
|
|
||||||
var isWindows = process.platform === 'win32';
|
|
||||||
|
|
||||||
export interface MergeTreesOptions { overwrite?: boolean; }
|
|
||||||
|
|
||||||
function outputFileSync(sourcePath: string, destPath: string) {
|
|
||||||
let dirname = path.dirname(destPath);
|
|
||||||
fse.mkdirsSync(dirname, {fs: fs});
|
|
||||||
symlinkOrCopySync(sourcePath, destPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
function pathOverwrittenError(path: string) {
|
|
||||||
const msg = 'Either remove the duplicate or enable the "overwrite" option for this merge.';
|
|
||||||
return new Error(`Duplicate path found while merging trees. Path: "${path}".\n${msg}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MergeTrees implements DiffingBroccoliPlugin {
|
|
||||||
private pathCache: {[key: string]: number[]} = Object.create(null);
|
|
||||||
public options: MergeTreesOptions;
|
|
||||||
private firstBuild: boolean = true;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public inputPaths: string[], public cachePath: string, options: MergeTreesOptions = {}) {
|
|
||||||
this.options = options || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
rebuild(treeDiffs: DiffResult[]) {
|
|
||||||
let overwrite = this.options.overwrite;
|
|
||||||
let pathsToEmit: string[] = [];
|
|
||||||
let pathsToRemove: string[] = [];
|
|
||||||
let emitted: {[key: string]: boolean} = Object.create(null);
|
|
||||||
let contains = (cache: number[], val: number) => {
|
|
||||||
for (let i = 0, ii = cache.length; i < ii; ++i) {
|
|
||||||
if (cache[i] === val) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let emit = (relativePath: string) => {
|
|
||||||
// ASSERT(!emitted[relativePath]);
|
|
||||||
pathsToEmit.push(relativePath);
|
|
||||||
emitted[relativePath] = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.firstBuild) {
|
|
||||||
this.firstBuild = false;
|
|
||||||
|
|
||||||
// Build initial cache
|
|
||||||
treeDiffs.reverse().forEach((treeDiff: DiffResult, index: number) => {
|
|
||||||
index = treeDiffs.length - 1 - index;
|
|
||||||
treeDiff.addedPaths.forEach((changedPath) => {
|
|
||||||
let cache = this.pathCache[changedPath];
|
|
||||||
if (cache === undefined) {
|
|
||||||
this.pathCache[changedPath] = [index];
|
|
||||||
pathsToEmit.push(changedPath);
|
|
||||||
} else if (overwrite) {
|
|
||||||
// ASSERT(contains(pathsToEmit, changedPath));
|
|
||||||
cache.unshift(index);
|
|
||||||
} else {
|
|
||||||
throw pathOverwrittenError(changedPath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Update cache
|
|
||||||
treeDiffs.reverse().forEach((treeDiff: DiffResult, index: number) => {
|
|
||||||
index = treeDiffs.length - 1 - index;
|
|
||||||
if (treeDiff.removedPaths) {
|
|
||||||
treeDiff.removedPaths.forEach((removedPath) => {
|
|
||||||
let cache = this.pathCache[removedPath];
|
|
||||||
// ASSERT(cache !== undefined);
|
|
||||||
// ASSERT(contains(cache, index));
|
|
||||||
if (cache[cache.length - 1] === index) {
|
|
||||||
pathsToRemove.push(path.join(this.cachePath, removedPath));
|
|
||||||
cache.pop();
|
|
||||||
if (cache.length === 0) {
|
|
||||||
this.pathCache[removedPath] = undefined;
|
|
||||||
} else if (!emitted[removedPath]) {
|
|
||||||
if (cache.length === 1 && !overwrite) {
|
|
||||||
throw pathOverwrittenError(removedPath);
|
|
||||||
}
|
|
||||||
emit(removedPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let pathsToUpdate = treeDiff.addedPaths;
|
|
||||||
|
|
||||||
if (isWindows) {
|
|
||||||
pathsToUpdate = pathsToUpdate.concat(treeDiff.changedPaths);
|
|
||||||
}
|
|
||||||
|
|
||||||
pathsToUpdate.forEach((changedPath) => {
|
|
||||||
let cache = this.pathCache[changedPath];
|
|
||||||
if (cache === undefined) {
|
|
||||||
// File was added
|
|
||||||
this.pathCache[changedPath] = [index];
|
|
||||||
emit(changedPath);
|
|
||||||
} else if (!contains(cache, index)) {
|
|
||||||
cache.push(index);
|
|
||||||
cache.sort((a, b) => a - b);
|
|
||||||
if (cache.length > 1 && !overwrite) {
|
|
||||||
throw pathOverwrittenError(changedPath);
|
|
||||||
}
|
|
||||||
if (cache[cache.length - 1] === index && !emitted[changedPath]) {
|
|
||||||
emit(changedPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pathsToRemove.forEach((destPath) => fse.removeSync(destPath));
|
|
||||||
pathsToEmit.forEach((emittedPath) => {
|
|
||||||
let cache = this.pathCache[emittedPath];
|
|
||||||
let destPath = path.join(this.cachePath, emittedPath);
|
|
||||||
let sourceIndex = cache[cache.length - 1];
|
|
||||||
let sourceInputPath = this.inputPaths[sourceIndex];
|
|
||||||
let sourcePath = path.join(sourceInputPath, emittedPath);
|
|
||||||
if (cache.length > 1) {
|
|
||||||
fse.removeSync(destPath);
|
|
||||||
}
|
|
||||||
outputFileSync(sourcePath, destPath);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default wrapDiffingPlugin(MergeTrees);
|
|
|
@ -1,59 +0,0 @@
|
||||||
import fs = require('fs');
|
|
||||||
import fse = require('fs-extra');
|
|
||||||
import path = require('path');
|
|
||||||
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
|
|
||||||
|
|
||||||
var minimatch = require('minimatch');
|
|
||||||
var FILE_ENCODING = {encoding: 'utf-8'};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intercepts each changed file and replaces its contents with
|
|
||||||
* the associated changes.
|
|
||||||
*/
|
|
||||||
class DiffingReplace implements DiffingBroccoliPlugin {
|
|
||||||
// TODO: define an interface for options
|
|
||||||
constructor(private inputPath: string, private cachePath: string, private options: any) {}
|
|
||||||
|
|
||||||
rebuild(treeDiff: DiffResult) {
|
|
||||||
var patterns = this.options.patterns;
|
|
||||||
var files = this.options.files;
|
|
||||||
|
|
||||||
treeDiff.addedPaths.concat(treeDiff.changedPaths).forEach((changedFilePath) => {
|
|
||||||
var sourceFilePath = path.join(this.inputPath, changedFilePath);
|
|
||||||
var destFilePath = path.join(this.cachePath, changedFilePath);
|
|
||||||
var destDirPath = path.dirname(destFilePath);
|
|
||||||
|
|
||||||
if (!fs.existsSync(destDirPath)) {
|
|
||||||
fse.mkdirpSync(destDirPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileMatches = files.some((filePath: string) => minimatch(changedFilePath, filePath));
|
|
||||||
if (fileMatches) {
|
|
||||||
var content = fs.readFileSync(sourceFilePath, FILE_ENCODING);
|
|
||||||
patterns.forEach((pattern: any) => {
|
|
||||||
var replacement = pattern.replacement;
|
|
||||||
if (typeof replacement === 'function') {
|
|
||||||
replacement = function(content: string) {
|
|
||||||
return pattern.replacement(content, changedFilePath);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
content = content.replace(pattern.match, replacement);
|
|
||||||
});
|
|
||||||
fs.writeFileSync(destFilePath, content, FILE_ENCODING);
|
|
||||||
} else if (!fs.existsSync(destFilePath)) {
|
|
||||||
try {
|
|
||||||
fs.symlinkSync(sourceFilePath, destFilePath);
|
|
||||||
} catch (e) {
|
|
||||||
fs.writeFileSync(destFilePath, fs.readFileSync(sourceFilePath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
treeDiff.removedPaths.forEach((removedFilePath) => {
|
|
||||||
var destFilePath = path.join(this.cachePath, removedFilePath);
|
|
||||||
fs.unlinkSync(destFilePath);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default wrapDiffingPlugin(DiffingReplace);
|
|
|
@ -1,42 +0,0 @@
|
||||||
import fs = require('fs');
|
|
||||||
let symlinkOrCopy = require('symlink-or-copy');
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stabilizes the inputPath for the following plugins in the build tree.
|
|
||||||
*
|
|
||||||
* All broccoli plugins that inherit from `broccoli-writer` or `broccoli-filter` change their
|
|
||||||
* outputPath during each rebuild.
|
|
||||||
*
|
|
||||||
* This means that all following plugins in the build tree can't rely on their inputPath being
|
|
||||||
* immutable. This results in breakage of any plugin that is not expecting such behavior.
|
|
||||||
*
|
|
||||||
* For example all `DiffingBroccoliPlugin`s expect their inputPath to be stable.
|
|
||||||
*
|
|
||||||
* By inserting this plugin into the tree after any misbehaving plugin, we can stabilize the
|
|
||||||
* inputPath for the following plugin in the tree and correct the surprising behavior.
|
|
||||||
*/
|
|
||||||
class TreeStabilizer implements BroccoliTree {
|
|
||||||
inputPath: string;
|
|
||||||
outputPath: string;
|
|
||||||
|
|
||||||
|
|
||||||
constructor(public inputTree: BroccoliTree) {}
|
|
||||||
|
|
||||||
|
|
||||||
rebuild() {
|
|
||||||
fs.rmdirSync(this.outputPath);
|
|
||||||
|
|
||||||
// TODO: investigate if we can use rename the directory instead to improve performance on
|
|
||||||
// Windows
|
|
||||||
symlinkOrCopy.sync(this.inputPath, this.outputPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
cleanup() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default function stabilizeTree(inputTree: BroccoliTree): BroccoliTree {
|
|
||||||
return new TreeStabilizer(inputTree);
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
import fs = require('fs');
|
|
||||||
import fse = require('fs-extra');
|
|
||||||
import path = require('path');
|
|
||||||
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
|
|
||||||
|
|
||||||
class TSToDartTranspiler implements DiffingBroccoliPlugin {
|
|
||||||
static includeExtensions = ['.ts'];
|
|
||||||
|
|
||||||
private transpiler: any /*ts2dart.Transpiler*/;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public inputPath: string, public cachePath: string,
|
|
||||||
public options: any /*ts2dart.TranspilerOptions*/) {
|
|
||||||
options.basePath = inputPath;
|
|
||||||
options.tsconfig = path.join(inputPath, options.tsconfig);
|
|
||||||
// Workaround for https://github.com/dart-lang/dart_style/issues/493
|
|
||||||
var ts2dart = require('ts2dart');
|
|
||||||
this.transpiler = new ts2dart.Transpiler(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
rebuild(treeDiff: DiffResult) {
|
|
||||||
let toEmit = [
|
|
||||||
path.resolve(this.inputPath, 'angular2/manual_typings/globals.d.ts'),
|
|
||||||
path.resolve(this.inputPath, 'angular2/typings/es6-promise/es6-promise.d.ts'),
|
|
||||||
path.resolve(this.inputPath, 'angular2/typings/es6-collections/es6-collections.d.ts')
|
|
||||||
];
|
|
||||||
let getDartFilePath = (path: string) => path.replace(/((\.js)|(\.ts))$/i, '.dart');
|
|
||||||
treeDiff.addedPaths.concat(treeDiff.changedPaths).forEach((changedPath) => {
|
|
||||||
let inputFilePath = path.resolve(this.inputPath, changedPath);
|
|
||||||
|
|
||||||
// Ignore files which don't need to be transpiled to Dart
|
|
||||||
let dartInputFilePath = getDartFilePath(inputFilePath);
|
|
||||||
if (fs.existsSync(dartInputFilePath)) return;
|
|
||||||
|
|
||||||
// Prepare to rebuild
|
|
||||||
toEmit.push(path.resolve(this.inputPath, changedPath));
|
|
||||||
});
|
|
||||||
|
|
||||||
treeDiff.removedPaths.forEach((removedPath) => {
|
|
||||||
let absolutePath = path.resolve(this.inputPath, removedPath);
|
|
||||||
|
|
||||||
// Ignore files which don't need to be transpiled to Dart
|
|
||||||
let dartInputFilePath = getDartFilePath(absolutePath);
|
|
||||||
if (fs.existsSync(dartInputFilePath)) return;
|
|
||||||
|
|
||||||
let dartOutputFilePath = getDartFilePath(removedPath);
|
|
||||||
fs.unlinkSync(path.join(this.cachePath, dartOutputFilePath));
|
|
||||||
});
|
|
||||||
this.transpiler.transpile(toEmit, this.cachePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default wrapDiffingPlugin(TSToDartTranspiler);
|
|
|
@ -1,387 +0,0 @@
|
||||||
import fs = require('fs');
|
|
||||||
import fse = require('fs-extra');
|
|
||||||
import path = require('path');
|
|
||||||
import * as ts from 'typescript';
|
|
||||||
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
|
|
||||||
import {MetadataCollector} from '../@angular/tsc-wrapped';
|
|
||||||
|
|
||||||
type FileRegistry = ts.Map<{version: number}>;
|
|
||||||
|
|
||||||
const FS_OPTS = {
|
|
||||||
encoding: 'utf-8'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sub-directory where the @internal typing files (.d.ts) are stored
|
|
||||||
export const INTERNAL_TYPINGS_PATH: string = 'internal_typings';
|
|
||||||
|
|
||||||
// Monkey patch the TS compiler to be able to re-emit files with @internal symbols
|
|
||||||
let tsEmitInternal: boolean = false;
|
|
||||||
|
|
||||||
const originalEmitFiles: Function = (<any>ts).emitFiles;
|
|
||||||
|
|
||||||
(<any>ts).emitFiles = function(resolver: any, host: any, targetSourceFile: any): any {
|
|
||||||
if (tsEmitInternal) {
|
|
||||||
const orignalgetCompilerOptions = host.getCompilerOptions;
|
|
||||||
host.getCompilerOptions = () => {
|
|
||||||
let options = clone(orignalgetCompilerOptions.call(host));
|
|
||||||
options.stripInternal = false;
|
|
||||||
options.outDir = `${options.outDir}/${INTERNAL_TYPINGS_PATH}`;
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return originalEmitFiles(resolver, host, targetSourceFile);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broccoli plugin that implements incremental Typescript compiler.
|
|
||||||
*
|
|
||||||
* It instantiates a typescript compiler instance that keeps all the state about the project and
|
|
||||||
* can re-emit only the files that actually changed.
|
|
||||||
*
|
|
||||||
* Limitations: only files that map directly to the changed source file via naming conventions are
|
|
||||||
* re-emitted. This primarily affects code that uses `const enum`s, because changing the enum value
|
|
||||||
* requires global emit, which can affect many files.
|
|
||||||
*/
|
|
||||||
class DiffingTSCompiler implements DiffingBroccoliPlugin {
|
|
||||||
private tsOpts: ts.CompilerOptions;
|
|
||||||
private fileRegistry: FileRegistry = Object.create(null);
|
|
||||||
private rootFilePaths: string[];
|
|
||||||
private tsServiceHost: ts.LanguageServiceHost;
|
|
||||||
private tsService: ts.LanguageService;
|
|
||||||
private metadataCollector: MetadataCollector;
|
|
||||||
private firstRun: boolean = true;
|
|
||||||
private previousRunFailed: boolean = false;
|
|
||||||
// Whether to generate the @internal typing files (they are only generated when `stripInternal` is
|
|
||||||
// true)
|
|
||||||
private genInternalTypings: boolean = false;
|
|
||||||
|
|
||||||
static includeExtensions = ['.ts'];
|
|
||||||
|
|
||||||
constructor(public inputPath: string, public cachePath: string, public options: any) {
|
|
||||||
// TODO: define an interface for options
|
|
||||||
if (options.rootFilePaths) {
|
|
||||||
this.rootFilePaths = options.rootFilePaths.splice(0);
|
|
||||||
delete options.rootFilePaths;
|
|
||||||
} else {
|
|
||||||
this.rootFilePaths = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.internalTypings) {
|
|
||||||
this.genInternalTypings = true;
|
|
||||||
delete options.internalTypings;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the conversion is a bit awkward, see https://github.com/Microsoft/TypeScript/issues/5276
|
|
||||||
// in 1.8 use convertCompilerOptionsFromJson
|
|
||||||
this.tsOpts =
|
|
||||||
ts.parseJsonConfigFileContent({compilerOptions: options, files: []}, null, null).options;
|
|
||||||
|
|
||||||
if ((<any>this.tsOpts).stripInternal === false) {
|
|
||||||
// @internal are included in the generated .d.ts, do not generate them separately
|
|
||||||
this.genInternalTypings = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tsOpts.rootDir = inputPath;
|
|
||||||
this.tsOpts.baseUrl = inputPath;
|
|
||||||
this.tsOpts.outDir = this.cachePath;
|
|
||||||
|
|
||||||
this.tsServiceHost = new CustomLanguageServiceHost(
|
|
||||||
this.tsOpts, this.rootFilePaths, this.fileRegistry, this.inputPath);
|
|
||||||
this.tsService = ts.createLanguageService(this.tsServiceHost, ts.createDocumentRegistry());
|
|
||||||
this.metadataCollector = new MetadataCollector();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
rebuild(treeDiff: DiffResult) {
|
|
||||||
let pathsToEmit: string[] = [];
|
|
||||||
let pathsWithErrors: string[] = [];
|
|
||||||
let errorMessages: string[] = [];
|
|
||||||
|
|
||||||
treeDiff.addedPaths.concat(treeDiff.changedPaths).forEach((tsFilePath) => {
|
|
||||||
if (!this.fileRegistry[tsFilePath]) {
|
|
||||||
this.fileRegistry[tsFilePath] = {version: 0};
|
|
||||||
this.rootFilePaths.push(tsFilePath);
|
|
||||||
} else {
|
|
||||||
this.fileRegistry[tsFilePath].version++;
|
|
||||||
}
|
|
||||||
|
|
||||||
pathsToEmit.push(path.join(this.inputPath, tsFilePath));
|
|
||||||
});
|
|
||||||
|
|
||||||
treeDiff.removedPaths.forEach((tsFilePath) => {
|
|
||||||
console.log('removing outputs for', tsFilePath);
|
|
||||||
|
|
||||||
this.rootFilePaths.splice(this.rootFilePaths.indexOf(tsFilePath), 1);
|
|
||||||
this.fileRegistry[tsFilePath] = null;
|
|
||||||
this.removeOutputFor(tsFilePath);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.firstRun) {
|
|
||||||
this.firstRun = false;
|
|
||||||
this.doFullBuild();
|
|
||||||
} else {
|
|
||||||
let program = this.tsService.getProgram();
|
|
||||||
tsEmitInternal = false;
|
|
||||||
pathsToEmit.forEach((tsFilePath) => {
|
|
||||||
let output = this.tsService.getEmitOutput(tsFilePath);
|
|
||||||
|
|
||||||
if (output.emitSkipped) {
|
|
||||||
let errorFound = this.collectErrors(tsFilePath);
|
|
||||||
if (errorFound) {
|
|
||||||
pathsWithErrors.push(tsFilePath);
|
|
||||||
errorMessages.push(errorFound);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
output.outputFiles.forEach(o => {
|
|
||||||
let destDirPath = path.dirname(o.name);
|
|
||||||
fse.mkdirsSync(destDirPath);
|
|
||||||
fs.writeFileSync(o.name, this.fixSourceMapSources(o.text), FS_OPTS);
|
|
||||||
if (endsWith(o.name, '.d.ts')) {
|
|
||||||
const sourceFile = program.getSourceFile(tsFilePath);
|
|
||||||
this.emitMetadata(o.name, sourceFile);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (pathsWithErrors.length) {
|
|
||||||
this.previousRunFailed = true;
|
|
||||||
var error =
|
|
||||||
new Error('Typescript found the following errors:\n' + errorMessages.join('\n'));
|
|
||||||
(<any>error)['showStack'] = false;
|
|
||||||
throw error;
|
|
||||||
} else if (this.previousRunFailed) {
|
|
||||||
this.doFullBuild();
|
|
||||||
} else if (this.genInternalTypings) {
|
|
||||||
// serialize the .d.ts files containing @internal symbols
|
|
||||||
tsEmitInternal = true;
|
|
||||||
pathsToEmit.forEach((tsFilePath) => {
|
|
||||||
let output = this.tsService.getEmitOutput(tsFilePath);
|
|
||||||
if (!output.emitSkipped) {
|
|
||||||
output.outputFiles.forEach(o => {
|
|
||||||
if (endsWith(o.name, '.d.ts')) {
|
|
||||||
let destDirPath = path.dirname(o.name);
|
|
||||||
fse.mkdirsSync(destDirPath);
|
|
||||||
fs.writeFileSync(o.name, this.fixSourceMapSources(o.text), FS_OPTS);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tsEmitInternal = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private collectErrors(tsFilePath: string): string {
|
|
||||||
let allDiagnostics = this.tsService.getCompilerOptionsDiagnostics()
|
|
||||||
.concat(this.tsService.getSyntacticDiagnostics(tsFilePath))
|
|
||||||
.concat(this.tsService.getSemanticDiagnostics(tsFilePath));
|
|
||||||
let errors: string[] = [];
|
|
||||||
|
|
||||||
allDiagnostics.forEach(diagnostic => {
|
|
||||||
let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
|
||||||
if (diagnostic.file) {
|
|
||||||
let {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
||||||
errors.push(` ${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
|
|
||||||
} else {
|
|
||||||
errors.push(` Error: ${message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (errors.length) {
|
|
||||||
return errors.join('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private doFullBuild() {
|
|
||||||
let program = this.tsService.getProgram();
|
|
||||||
let typeChecker = program.getTypeChecker();
|
|
||||||
let diagnostics: ts.Diagnostic[] = [];
|
|
||||||
tsEmitInternal = false;
|
|
||||||
|
|
||||||
let emitResult = program.emit(undefined, (absoluteFilePath, fileContent) => {
|
|
||||||
fse.mkdirsSync(path.dirname(absoluteFilePath));
|
|
||||||
fs.writeFileSync(absoluteFilePath, this.fixSourceMapSources(fileContent), FS_OPTS);
|
|
||||||
if (endsWith(absoluteFilePath, '.d.ts')) {
|
|
||||||
// TODO: Use sourceFile from the callback if
|
|
||||||
// https://github.com/Microsoft/TypeScript/issues/7438
|
|
||||||
// is taken
|
|
||||||
const originalFile = absoluteFilePath.replace(this.tsOpts.outDir, this.tsOpts.rootDir)
|
|
||||||
.replace(/\.d\.ts$/, '.ts');
|
|
||||||
const sourceFile = program.getSourceFile(originalFile);
|
|
||||||
this.emitMetadata(absoluteFilePath, sourceFile);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.genInternalTypings) {
|
|
||||||
// serialize the .d.ts files containing @internal symbols
|
|
||||||
tsEmitInternal = true;
|
|
||||||
program.emit(undefined, (absoluteFilePath, fileContent) => {
|
|
||||||
if (endsWith(absoluteFilePath, '.d.ts')) {
|
|
||||||
fse.mkdirsSync(path.dirname(absoluteFilePath));
|
|
||||||
fs.writeFileSync(absoluteFilePath, fileContent, FS_OPTS);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tsEmitInternal = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (emitResult.emitSkipped) {
|
|
||||||
let allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
|
|
||||||
let errorMessages: string[] = [];
|
|
||||||
|
|
||||||
allDiagnostics.forEach(diagnostic => {
|
|
||||||
var pos = '';
|
|
||||||
if (diagnostic.file) {
|
|
||||||
var {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
||||||
pos = `${diagnostic.file.fileName} (${line + 1}, ${character + 1}): `
|
|
||||||
}
|
|
||||||
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
|
||||||
errorMessages.push(` ${pos}${message}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (errorMessages.length) {
|
|
||||||
this.previousRunFailed = true;
|
|
||||||
var error =
|
|
||||||
new Error('Typescript found the following errors:\n' + errorMessages.join('\n'));
|
|
||||||
(<any>error)['showStack'] = false;
|
|
||||||
throw error;
|
|
||||||
} else {
|
|
||||||
this.previousRunFailed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emit a .metadata.json file to correspond to the .d.ts file if the module contains classes that
|
|
||||||
* use decorators or exported constants.
|
|
||||||
*/
|
|
||||||
private emitMetadata(dtsFileName: string, sourceFile: ts.SourceFile) {
|
|
||||||
if (sourceFile) {
|
|
||||||
const metadata = this.metadataCollector.getMetadata(sourceFile);
|
|
||||||
if (metadata && metadata.metadata) {
|
|
||||||
const metadataText = JSON.stringify(metadata);
|
|
||||||
const metadataFileName = dtsFileName.replace(/\.d.ts$/, '.metadata.json');
|
|
||||||
fs.writeFileSync(metadataFileName, metadataText, FS_OPTS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* There is a bug in TypeScript 1.6, where the sourceRoot and inlineSourceMap properties
|
|
||||||
* are exclusive. This means that the sources property always contains relative paths
|
|
||||||
* (e.g, ../../../../angular2/src/di/injector.ts).
|
|
||||||
*
|
|
||||||
* Here, we normalize the sources property and remove the ../../../
|
|
||||||
*
|
|
||||||
* This issue is fixed in https://github.com/Microsoft/TypeScript/pull/5620.
|
|
||||||
* Once we switch to TypeScript 1.8, we can remove this method.
|
|
||||||
*/
|
|
||||||
private fixSourceMapSources(content: string): string {
|
|
||||||
try {
|
|
||||||
const marker = '//# sourceMappingURL=data:application/json;base64,';
|
|
||||||
const index = content.indexOf(marker);
|
|
||||||
if (index == -1) return content;
|
|
||||||
|
|
||||||
const base = content.substring(0, index + marker.length);
|
|
||||||
const sourceMapBit =
|
|
||||||
new Buffer(content.substring(index + marker.length), 'base64').toString('utf8');
|
|
||||||
const sourceMaps = JSON.parse(sourceMapBit);
|
|
||||||
const source = sourceMaps.sources[0];
|
|
||||||
sourceMaps.sources = [source.substring(source.lastIndexOf('../') + 3)];
|
|
||||||
return `${base}${new Buffer(JSON.stringify(sourceMaps)).toString('base64')}`;
|
|
||||||
} catch (e) {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private removeOutputFor(tsFilePath: string) {
|
|
||||||
let absoluteJsFilePath = path.join(this.cachePath, tsFilePath.replace(/\.ts$/, '.js'));
|
|
||||||
let absoluteMapFilePath = path.join(this.cachePath, tsFilePath.replace(/.ts$/, '.js.map'));
|
|
||||||
let absoluteDtsFilePath = path.join(this.cachePath, tsFilePath.replace(/\.ts$/, '.d.ts'));
|
|
||||||
|
|
||||||
if (fs.existsSync(absoluteJsFilePath)) {
|
|
||||||
fs.unlinkSync(absoluteJsFilePath);
|
|
||||||
if (fs.existsSync(absoluteMapFilePath)) {
|
|
||||||
// source map could be inline or not generated
|
|
||||||
fs.unlinkSync(absoluteMapFilePath);
|
|
||||||
}
|
|
||||||
fs.unlinkSync(absoluteDtsFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CustomLanguageServiceHost implements ts.LanguageServiceHost {
|
|
||||||
private currentDirectory: string;
|
|
||||||
private defaultLibFilePath: string;
|
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private compilerOptions: ts.CompilerOptions, private fileNames: string[],
|
|
||||||
private fileRegistry: FileRegistry, private treeInputPath: string) {
|
|
||||||
this.currentDirectory = process.cwd();
|
|
||||||
this.defaultLibFilePath = ts.getDefaultLibFilePath(compilerOptions).replace(/\\/g, '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
getScriptFileNames(): string[] {
|
|
||||||
return this.fileNames.map(f => path.join(this.treeInputPath, f));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
getScriptVersion(fileName: string): string {
|
|
||||||
if (startsWith(fileName, this.treeInputPath)) {
|
|
||||||
const key = fileName.substr(this.treeInputPath.length + 1);
|
|
||||||
return this.fileRegistry[key] && this.fileRegistry[key].version.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
getScriptSnapshot(tsFilePath: string): ts.IScriptSnapshot {
|
|
||||||
// TypeScript seems to request lots of bogus paths during import path lookup and resolution,
|
|
||||||
// so we we just return undefined when the path is not correct.
|
|
||||||
|
|
||||||
// Ensure it is in the input tree or a lib.d.ts file.
|
|
||||||
if (!startsWith(tsFilePath, this.treeInputPath) && !tsFilePath.match(/\/lib(\..*)*.d\.ts$/)) {
|
|
||||||
if (fs.existsSync(tsFilePath)) {
|
|
||||||
console.log('Rejecting', tsFilePath, '. File is not in the input tree.');
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure it exists
|
|
||||||
if (!fs.existsSync(tsFilePath)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ts.ScriptSnapshot.fromString(fs.readFileSync(tsFilePath, FS_OPTS));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
getCurrentDirectory(): string { return this.currentDirectory; }
|
|
||||||
|
|
||||||
getCompilationSettings(): ts.CompilerOptions { return this.compilerOptions; }
|
|
||||||
|
|
||||||
getDefaultLibFileName(options: ts.CompilerOptions): string {
|
|
||||||
// ignore options argument, options should not change during the lifetime of the plugin
|
|
||||||
return this.defaultLibFilePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default wrapDiffingPlugin(DiffingTSCompiler);
|
|
||||||
|
|
||||||
function clone<T>(object: T): T {
|
|
||||||
const result: any = {};
|
|
||||||
for (const id in object) {
|
|
||||||
result[id] = (<any>object)[id];
|
|
||||||
}
|
|
||||||
return <T>result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function startsWith(str: string, substring: string): boolean {
|
|
||||||
return str.substring(0, substring.length) === substring;
|
|
||||||
}
|
|
||||||
|
|
||||||
function endsWith(str: string, substring: string): boolean {
|
|
||||||
return str.indexOf(substring, str.length - substring.length) !== -1;
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
declare module 'broccoli-writer' {
|
|
||||||
class Writer {
|
|
||||||
write(readTree: (tree: BroccoliTree) => Promise<string>, destDir: string): Promise<any>;
|
|
||||||
}
|
|
||||||
export = Writer;
|
|
||||||
}
|
|
|
@ -1,157 +0,0 @@
|
||||||
interface BroccoliTree {
|
|
||||||
/**
|
|
||||||
* Contains the fs path for the input tree when the plugin takes only one input tree.
|
|
||||||
*
|
|
||||||
* For plugins that take multiple trees see the `inputPaths` property.
|
|
||||||
*
|
|
||||||
* This property is set just before the first rebuild and doesn't change afterwards.
|
|
||||||
*/
|
|
||||||
inputPath: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains the array of fs paths for input trees.
|
|
||||||
*
|
|
||||||
* For plugins that take only one input tree, it might be more convenient to use the `inputPath`
|
|
||||||
*property instead.
|
|
||||||
*
|
|
||||||
* This property is set just before the first rebuild and doesn't change afterwards, unless
|
|
||||||
* plugins themselves change it.
|
|
||||||
*
|
|
||||||
* If the inputPath is outside of broccoli's temp directory, then it's lifetime is not managed by
|
|
||||||
*the builder.
|
|
||||||
* If the inputPath is within broccoli's temp directory it is an outputPath (and output directory)
|
|
||||||
*of another plugin.
|
|
||||||
* This means that while the `outputPath` doesn't change, the underlying directory is frequently
|
|
||||||
*recreated.
|
|
||||||
*/
|
|
||||||
inputPaths?: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains the fs paths for the output trees.
|
|
||||||
*
|
|
||||||
* This property is set just before the first rebuild and doesn't change afterwards, unless the
|
|
||||||
* plugins themselves change it.
|
|
||||||
*
|
|
||||||
* The underlying directory is also created by the builder just before the first rebuild.
|
|
||||||
* This directory is destroyed and recreated upon each rebuild.
|
|
||||||
*/
|
|
||||||
outputPath?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains the fs paths for a cache directory available to the plugin.
|
|
||||||
*
|
|
||||||
* This property is set just before the first rebuild and doesn't change afterwards.
|
|
||||||
*
|
|
||||||
* The underlying directory is also created by the builder just before the first rebuild.
|
|
||||||
* The lifetime of the directory is associated with the lifetime of the plugin.
|
|
||||||
*/
|
|
||||||
cachePath?: string;
|
|
||||||
|
|
||||||
inputTree?: BroccoliTree;
|
|
||||||
inputTrees?: BroccoliTree[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trees which implement the rebuild api are wrapped automatically for api compat,
|
|
||||||
* and `newStyleTree` keeps a reference to the original unwrapped tree.
|
|
||||||
*/
|
|
||||||
newStyleTree?: BroccoliTree;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Description or name of the plugin used for reporting.
|
|
||||||
*
|
|
||||||
* If missing `tree.constructor.name` is usually used instead.
|
|
||||||
*/
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
rebuild(): (Promise<any>|void);
|
|
||||||
cleanup(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
interface OldBroccoliTree {
|
|
||||||
read?(readTree: (tree: BroccoliTree) => Promise<string>): (Promise<string>|string);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface BroccoliBuilder {
|
|
||||||
/**
|
|
||||||
* Triggers a build and returns a promise for the build result
|
|
||||||
*/
|
|
||||||
build(): Promise<BuildResult>;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up the whole build tree by calling `.cleanup()` method on all trees that are part of the
|
|
||||||
* pipeline.
|
|
||||||
*/
|
|
||||||
cleanup(): Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
interface BuildResult {
|
|
||||||
/**
|
|
||||||
* Directory that contains result of the build.
|
|
||||||
*
|
|
||||||
* This directory will contains symlinks, so it is not safe to just use it as is.
|
|
||||||
*
|
|
||||||
* Use `copy-dereference` npm package to create a safe-to-use replica of the build artifacts.
|
|
||||||
*/
|
|
||||||
directory: string;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The DAG (graph) of all trees in the build pipeline.
|
|
||||||
*/
|
|
||||||
graph: BroccoliNode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Total time it took to make the build.
|
|
||||||
*/
|
|
||||||
totalTime: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface BroccoliNode {
|
|
||||||
///**
|
|
||||||
// * Id of the current node
|
|
||||||
// */
|
|
||||||
// id: number; //only in master
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Time spent processing the current node during a single rebuild.
|
|
||||||
*/
|
|
||||||
selfTime: number;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Time spent processing the current node and its subtrees during a single rebuild.
|
|
||||||
*/
|
|
||||||
totalTime: number;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tree associated with the current node.
|
|
||||||
*/
|
|
||||||
tree: BroccoliTree;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Child nodes with references to trees that are input for the tree of the current node.
|
|
||||||
*/
|
|
||||||
subtrees: BroccoliNode[];
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parent nodes with references to trees that are consume the output of processing the current
|
|
||||||
* tree.
|
|
||||||
*/
|
|
||||||
parents: BroccoliNode[];
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Path to the directory containing the output of processing the current tree.
|
|
||||||
*/
|
|
||||||
directory: string;
|
|
||||||
}
|
|
|
@ -1,186 +0,0 @@
|
||||||
/// <reference path="broccoli.d.ts" />
|
|
||||||
|
|
||||||
import fs = require('fs');
|
|
||||||
import fse = require('fs-extra');
|
|
||||||
import path = require('path');
|
|
||||||
import {TreeDiffer, DiffResult} from './tree-differ';
|
|
||||||
import stabilizeTree from './broccoli-tree-stabilizer';
|
|
||||||
let symlinkOrCopy = require('symlink-or-copy');
|
|
||||||
|
|
||||||
export {DiffResult} from './tree-differ';
|
|
||||||
|
|
||||||
export type PluginClass = any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes writing diffing plugins easy.
|
|
||||||
*
|
|
||||||
* Factory method that takes a class that implements the DiffingBroccoliPlugin interface and returns
|
|
||||||
* an instance of BroccoliTree.
|
|
||||||
*/
|
|
||||||
export function wrapDiffingPlugin(pluginClass: PluginClass): DiffingPluginWrapperFactory {
|
|
||||||
return function() { return new DiffingPluginWrapper(pluginClass, arguments) };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface DiffingBroccoliPlugin {
|
|
||||||
rebuild(diff: (DiffResult|DiffResult[])): (Promise<DiffResult|void>|DiffResult|void);
|
|
||||||
cleanup?(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export type DiffingPluginWrapperFactory =
|
|
||||||
(inputTrees: (BroccoliTree | BroccoliTree[]), options?: any) => BroccoliTree;
|
|
||||||
|
|
||||||
|
|
||||||
class DiffingPluginWrapper implements BroccoliTree {
|
|
||||||
treeDiffer: TreeDiffer = null;
|
|
||||||
treeDiffers: TreeDiffer[] = null;
|
|
||||||
initialized = false;
|
|
||||||
wrappedPlugin: DiffingBroccoliPlugin = null;
|
|
||||||
inputTree: BroccoliTree = null;
|
|
||||||
inputTrees: BroccoliTree[] = null;
|
|
||||||
description: string = null;
|
|
||||||
|
|
||||||
// props monkey-patched by broccoli builder:
|
|
||||||
inputPath: string = null;
|
|
||||||
inputPaths: string[] = null;
|
|
||||||
cachePath: string = null;
|
|
||||||
outputPath: string = null;
|
|
||||||
|
|
||||||
private diffResult: DiffResult = null;
|
|
||||||
|
|
||||||
constructor(private pluginClass: PluginClass, private wrappedPluginArguments: IArguments) {
|
|
||||||
if (Array.isArray(wrappedPluginArguments[0])) {
|
|
||||||
this.inputTrees = this.stabilizeTrees(wrappedPluginArguments[0]);
|
|
||||||
} else {
|
|
||||||
this.inputTree = this.stabilizeTree(wrappedPluginArguments[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.description = this.pluginClass.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getDiffResult(): (DiffResult|DiffResult[]) {
|
|
||||||
let returnOrCalculateDiffResult = (tree: BroccoliTree, index: number) => {
|
|
||||||
// returnOrCalculateDiffResult will do one of two things:
|
|
||||||
//
|
|
||||||
// If `this.diffResult` is null, calculate a DiffResult using TreeDiffer
|
|
||||||
// for the input tree.
|
|
||||||
//
|
|
||||||
// Otherwise, `this.diffResult` was produced from the output of the
|
|
||||||
// inputTree's rebuild() method, and can be used without being checked.
|
|
||||||
// Set `this.diffResult` to null and return the previously stored value.
|
|
||||||
let diffResult = this.diffResult;
|
|
||||||
if (diffResult) return diffResult;
|
|
||||||
let differ = index === -1 ? this.treeDiffer : this.treeDiffers[index];
|
|
||||||
return differ.diffTree();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.inputTrees) {
|
|
||||||
return this.inputTrees.map(returnOrCalculateDiffResult);
|
|
||||||
} else if (this.inputTree) {
|
|
||||||
return returnOrCalculateDiffResult(this.inputTree, -1);
|
|
||||||
} else {
|
|
||||||
throw new Error('Missing TreeDiffer');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private maybeStoreDiffResult(value: (DiffResult|void)) {
|
|
||||||
if (!(value instanceof DiffResult)) value = null;
|
|
||||||
this.diffResult = <DiffResult>(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
rebuild(): (Promise<any>|void) {
|
|
||||||
try {
|
|
||||||
let firstRun = !this.initialized;
|
|
||||||
this.init();
|
|
||||||
|
|
||||||
let diffResult = this.getDiffResult();
|
|
||||||
|
|
||||||
let result = this.wrappedPlugin.rebuild(diffResult);
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
let resultPromise = <Promise<DiffResult|void>>(result);
|
|
||||||
if (resultPromise.then) {
|
|
||||||
// rebuild() -> Promise<>
|
|
||||||
return resultPromise.then((result: (DiffResult | void)) => {
|
|
||||||
this.maybeStoreDiffResult(result);
|
|
||||||
this.relinkOutputAndCachePaths();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.maybeStoreDiffResult(<(DiffResult | void)>(result));
|
|
||||||
this.relinkOutputAndCachePaths();
|
|
||||||
} catch (e) {
|
|
||||||
e.message = `[${this.description}]: ${e.message}`;
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
if (this.wrappedPlugin && this.wrappedPlugin.cleanup) {
|
|
||||||
this.wrappedPlugin.cleanup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private relinkOutputAndCachePaths() {
|
|
||||||
// just symlink the cache and output tree
|
|
||||||
fs.rmdirSync(this.outputPath);
|
|
||||||
symlinkOrCopy.sync(this.cachePath, this.outputPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private init() {
|
|
||||||
if (!this.initialized) {
|
|
||||||
let includeExtensions = this.pluginClass.includeExtensions || [];
|
|
||||||
let excludeExtensions = this.pluginClass.excludeExtensions || [];
|
|
||||||
let description = this.description;
|
|
||||||
this.initialized = true;
|
|
||||||
if (this.inputPaths) {
|
|
||||||
this.treeDiffers = this.inputPaths.map(
|
|
||||||
(inputPath) =>
|
|
||||||
new TreeDiffer(description, inputPath, includeExtensions, excludeExtensions));
|
|
||||||
} else if (this.inputPath) {
|
|
||||||
this.treeDiffer =
|
|
||||||
new TreeDiffer(description, this.inputPath, includeExtensions, excludeExtensions);
|
|
||||||
}
|
|
||||||
this.wrappedPlugin = new this.pluginClass(
|
|
||||||
this.inputPaths || this.inputPath, this.cachePath, this.wrappedPluginArguments[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private stabilizeTrees(trees: BroccoliTree[]) {
|
|
||||||
// Prevent extensions to prevent array from being mutated from the outside.
|
|
||||||
// For-loop used to avoid re-allocating a new array.
|
|
||||||
var stableTrees: BroccoliTree[] = [];
|
|
||||||
for (let i = 0; i < trees.length; ++i) {
|
|
||||||
// ignore null/undefined input tries in order to support conditional build pipelines
|
|
||||||
if (trees[i]) {
|
|
||||||
stableTrees.push(this.stabilizeTree(trees[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stableTrees.length === 0) {
|
|
||||||
throw new Error('No input trees provided!');
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.freeze(stableTrees);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private stabilizeTree(tree: BroccoliTree) {
|
|
||||||
// Ignore all DiffingPlugins as they are already stable, for others we don't know for sure
|
|
||||||
// so we need to stabilize them.
|
|
||||||
// Since it's not safe to use instanceof operator in node, we are checking the constructor.name.
|
|
||||||
//
|
|
||||||
// New-style/rebuild trees should always be stable.
|
|
||||||
let isNewStyleTree =
|
|
||||||
!!(tree['newStyleTree'] || typeof tree.rebuild === 'function' ||
|
|
||||||
(<any>tree)['isReadAPICompatTree'] || (<any>tree).constructor['name'] === 'Funnel');
|
|
||||||
|
|
||||||
return isNewStyleTree ? tree : stabilizeTree(tree);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
<script src="es6-shim.js"></script>
|
|
||||||
<script src="system.src.js"></script>
|
|
||||||
<script>
|
|
||||||
var scriptUrls;
|
|
||||||
var loadRuntimePackages = [
|
|
||||||
'benchmarks',
|
|
||||||
'playground',
|
|
||||||
// TODO(rado): These helpers don't end up in the bundle, thus they should
|
|
||||||
// not even be in src/*. Move them!
|
|
||||||
'angular2/src/testing/benchmark_util',
|
|
||||||
'angular2/src/facade/browser',
|
|
||||||
'rxjs'
|
|
||||||
];
|
|
||||||
if (@@USE_BUNDLES) {
|
|
||||||
scriptUrls = [
|
|
||||||
'/bundle/angular2-polyfills.js',
|
|
||||||
'/bundle/angular2.dev.js',
|
|
||||||
'/bundle/http.js',
|
|
||||||
'/bundle/router.dev.js',
|
|
||||||
'/rxjs/bundles/Rx.js'
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
console.warn("Not using the Angular 2 bundle. "+
|
|
||||||
"Don't use this configuration for e2e/performance tests!")
|
|
||||||
loadRuntimePackages.push('angular2');
|
|
||||||
scriptUrls = [
|
|
||||||
'Reflect.js',
|
|
||||||
'zone.js',
|
|
||||||
'long-stack-trace-zone.js'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
var systemJsPackages = {};
|
|
||||||
loadRuntimePackages.forEach(function(pck) {
|
|
||||||
systemJsPackages[pck] = {defaultExtension: 'js'};
|
|
||||||
});
|
|
||||||
System.config({
|
|
||||||
baseURL: '/',
|
|
||||||
packages: systemJsPackages
|
|
||||||
});
|
|
||||||
for (var i=0; i<scriptUrls.length; i++) {
|
|
||||||
document.write('<script src="'+scriptUrls[i]+'"></'+'script>');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
var filename = '@@PATH/@@FILENAME';
|
|
||||||
System.import(filename).then(function(m) { m.main && m.main(); }, console.error.bind(console));
|
|
||||||
</script>
|
|
|
@ -1,48 +0,0 @@
|
||||||
<script src="es6-shim.js"></script>
|
|
||||||
<script src="url_params_to_form.js"></script>
|
|
||||||
<script src="system.src.js"></script>
|
|
||||||
<script>
|
|
||||||
var scriptUrls;
|
|
||||||
var loadRuntimePackages = [
|
|
||||||
'benchmarks',
|
|
||||||
'playground',
|
|
||||||
// TODO(rado): These helpers don't end up in the bundle, thus they should
|
|
||||||
// not even be in src/*. Move them!
|
|
||||||
'angular2/src/testing/benchmark_util',
|
|
||||||
'angular2/src/facade/browser',
|
|
||||||
'rxjs'
|
|
||||||
];
|
|
||||||
if (@@USE_BUNDLES) {
|
|
||||||
scriptUrls = [
|
|
||||||
'/bundle/angular2-polyfills.js',
|
|
||||||
'/bundle/angular2.dev.js',
|
|
||||||
'/bundle/http.js',
|
|
||||||
'/bundle/router.dev.js',
|
|
||||||
'/rxjs/bundles/Rx.js'
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
console.warn("Not using the Angular 2 bundle. "+
|
|
||||||
"Don't use this configuration for e2e/performance tests!")
|
|
||||||
loadRuntimePackages.push('angular2');
|
|
||||||
scriptUrls = [
|
|
||||||
'Reflect.js',
|
|
||||||
'zone.js',
|
|
||||||
'long-stack-trace-zone.js'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
var systemJsPackages = {};
|
|
||||||
loadRuntimePackages.forEach(function(pck) {
|
|
||||||
systemJsPackages[pck] = {defaultExtension: 'js'};
|
|
||||||
});
|
|
||||||
System.config({
|
|
||||||
baseURL: '/',
|
|
||||||
packages: systemJsPackages
|
|
||||||
});
|
|
||||||
for (var i=0; i<scriptUrls.length; i++) {
|
|
||||||
document.write('<script src="'+scriptUrls[i]+'"></'+'script>');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
var filename = '@@PATH/@@FILENAME';
|
|
||||||
System.import(filename).then(function(m) { m.main && m.main(); }, console.error.bind(console));
|
|
||||||
</script>
|
|
|
@ -1,14 +0,0 @@
|
||||||
<script src="es6-shim.js"></script>
|
|
||||||
<script src="zone.js"></script>
|
|
||||||
<script src="long-stack-trace-zone.js"></script>
|
|
||||||
<script src="angular.js"></script>
|
|
||||||
<script src="url_params_to_form.js"></script>
|
|
||||||
<script src="system.src.js"></script>
|
|
||||||
<script>
|
|
||||||
System.config({
|
|
||||||
baseURL: '/',
|
|
||||||
defaultJSExtensions: true
|
|
||||||
});
|
|
||||||
var filename = '@@PATH/@@FILENAME';
|
|
||||||
System.import(filename).then(function(m) { m.main && m.main(); }, console.error.bind(console));
|
|
||||||
</script>
|
|
|
@ -1,11 +0,0 @@
|
||||||
var fs = require('fs');
|
|
||||||
var path = require('path');
|
|
||||||
|
|
||||||
module.exports = read;
|
|
||||||
function read(file: string) {
|
|
||||||
var content = fs.readFileSync(
|
|
||||||
path.join('tools/broccoli/html-replace', file + '.html'), {encoding: 'utf-8'});
|
|
||||||
// TODO(broccoli): we don't really need this, it's here to make the output match the
|
|
||||||
// tools/build/html
|
|
||||||
return content.substring(0, content.lastIndexOf('\n'));
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
importScripts("es6-shim.js", "zone.js", "long-stack-trace-zone.js", "system.src.js",
|
|
||||||
"Reflect.js");
|
|
|
@ -1,11 +0,0 @@
|
||||||
var fs = require('fs');
|
|
||||||
var path = require('path');
|
|
||||||
|
|
||||||
module.exports = readJs;
|
|
||||||
function readJs(file: string) {
|
|
||||||
var content =
|
|
||||||
fs.readFileSync(path.join('tools/broccoli/js-replace', file + '.js'), {encoding: 'utf-8'});
|
|
||||||
// TODO(broccoli): we don't really need this, it's here to make the output match the
|
|
||||||
// tools/build/html
|
|
||||||
return content.substring(0, content.lastIndexOf('\n'));
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
/// <reference path="./broccoli-writer.d.ts" />
|
|
||||||
|
|
||||||
import Writer = require('broccoli-writer');
|
|
||||||
import fs = require('fs');
|
|
||||||
import fsx = require('fs-extra');
|
|
||||||
var minimatch = require('minimatch');
|
|
||||||
var path = require('path');
|
|
||||||
var glob = require('glob');
|
|
||||||
|
|
||||||
export interface MultiCopyOptions {
|
|
||||||
/** The path of the file to copy. */
|
|
||||||
srcPath: string;
|
|
||||||
/** A list of glob patterns of folders to copy to, matched against the input tree. */
|
|
||||||
targetPatterns: string[];
|
|
||||||
/** List of glob patterns to *not* copy to, matched against the matches from `targetPatterns`. */
|
|
||||||
exclude?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A writer that copies an input file from an input path into (potentially many) output locations
|
|
||||||
* given by glob patterns, .
|
|
||||||
*/
|
|
||||||
export class MultiCopy extends Writer {
|
|
||||||
constructor(private inputTree: BroccoliTree, private options: MultiCopyOptions) { super(); }
|
|
||||||
|
|
||||||
write(readTree: (tree: BroccoliTree) => Promise<string>, destDir: string): Promise<any> {
|
|
||||||
return readTree(this.inputTree).then((inputPath: string) => {
|
|
||||||
var fileName = path.basename(this.options.srcPath);
|
|
||||||
var data = fs.readFileSync(path.join(inputPath, this.options.srcPath), 'utf-8');
|
|
||||||
|
|
||||||
this.options.targetPatterns.forEach(pattern => {
|
|
||||||
var paths: string[] = glob.sync(pattern);
|
|
||||||
paths = paths.filter(p => fs.statSync(p).isDirectory());
|
|
||||||
if (this.options.exclude) {
|
|
||||||
paths = paths.filter(p => !this.options.exclude.some((excl) => minimatch(p, excl)));
|
|
||||||
}
|
|
||||||
paths.forEach(p => {
|
|
||||||
var folder = path.join(destDir, p);
|
|
||||||
fsx.mkdirsSync(folder);
|
|
||||||
var outputPath = path.join(folder, fileName);
|
|
||||||
fs.writeFileSync(outputPath, data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,380 +0,0 @@
|
||||||
let mockfs = require('mock-fs');
|
|
||||||
import fs = require('fs');
|
|
||||||
import path = require('path');
|
|
||||||
import {TreeDiffer} from './tree-differ';
|
|
||||||
|
|
||||||
|
|
||||||
describe('TreeDiffer', () => {
|
|
||||||
|
|
||||||
afterEach(() => mockfs.restore());
|
|
||||||
|
|
||||||
|
|
||||||
describe('diff of added and changed files', () => {
|
|
||||||
|
|
||||||
it('should list all files (but no directories) during the first diff', () => {
|
|
||||||
let testDir = {
|
|
||||||
'dir1': {
|
|
||||||
'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)}),
|
|
||||||
'file-2.txt': mockfs.file({content: 'file-2.txt content', mtime: new Date(1000)}),
|
|
||||||
'subdir-1': {
|
|
||||||
'file-1.1.txt': mockfs.file({content: 'file-1.1.txt content', mtime: new Date(1000)})
|
|
||||||
},
|
|
||||||
'empty-dir': {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let differ = new TreeDiffer('testLabel', 'dir1');
|
|
||||||
|
|
||||||
let diffResult = differ.diffTree();
|
|
||||||
|
|
||||||
expect(diffResult.addedPaths).toEqual([
|
|
||||||
'file-1.txt', 'file-2.txt', 'subdir-1' + path.sep + 'file-1.1.txt'
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(diffResult.changedPaths).toEqual([]);
|
|
||||||
expect(diffResult.removedPaths).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should return empty diff if nothing has changed', () => {
|
|
||||||
let testDir = {
|
|
||||||
'dir1': {
|
|
||||||
'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)}),
|
|
||||||
'file-2.txt': mockfs.file({content: 'file-2.txt content', mtime: new Date(1000)}),
|
|
||||||
'subdir-1': {
|
|
||||||
'file-1.1.txt': mockfs.file({content: 'file-1.1.txt content', mtime: new Date(1000)})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let differ = new TreeDiffer('testLabel', 'dir1');
|
|
||||||
|
|
||||||
let diffResult = differ.diffTree();
|
|
||||||
|
|
||||||
expect(diffResult.addedPaths).not.toEqual([]);
|
|
||||||
expect(diffResult.changedPaths).toEqual([]);
|
|
||||||
expect(diffResult.removedPaths).toEqual([]);
|
|
||||||
|
|
||||||
diffResult = differ.diffTree();
|
|
||||||
|
|
||||||
expect(diffResult.addedPaths).toEqual([]);
|
|
||||||
expect(diffResult.changedPaths).toEqual([]);
|
|
||||||
expect(diffResult.removedPaths).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should list only changed files during the subsequent diffs', () => {
|
|
||||||
let testDir = {
|
|
||||||
'dir1': {
|
|
||||||
'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)}),
|
|
||||||
'file-2.txt': mockfs.file({content: 'file-2.txt content', mtime: new Date(1000)}),
|
|
||||||
'subdir-1': {
|
|
||||||
'file-1.1.txt':
|
|
||||||
mockfs.file({content: 'file-1.1.txt content', mtime: new Date(1000)})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let differ = new TreeDiffer('testLabel', 'dir1');
|
|
||||||
|
|
||||||
let diffResult = differ.diffTree();
|
|
||||||
|
|
||||||
expect(diffResult.addedPaths).toEqual([
|
|
||||||
'file-1.txt', 'file-2.txt', 'subdir-1' + path.sep + 'file-1.1.txt'
|
|
||||||
]);
|
|
||||||
|
|
||||||
// change two files
|
|
||||||
testDir['dir1']['file-1.txt'] = mockfs.file({content: 'new content', mtime: new Date(1000)});
|
|
||||||
testDir['dir1']['subdir-1']['file-1.1.txt'] =
|
|
||||||
mockfs.file({content: 'file-1.1.txt content', mtime: new Date(9999)});
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
diffResult = differ.diffTree();
|
|
||||||
|
|
||||||
expect(diffResult.changedPaths).toEqual([
|
|
||||||
'file-1.txt', 'subdir-1' + path.sep + 'file-1.1.txt'
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(diffResult.removedPaths).toEqual([]);
|
|
||||||
|
|
||||||
// change one file
|
|
||||||
testDir['dir1']['file-1.txt'] = mockfs.file({content: 'super new', mtime: new Date(1000)});
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
diffResult = differ.diffTree();
|
|
||||||
expect(diffResult.changedPaths).toEqual(['file-1.txt']);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should handle changes via symbolic links', () => {
|
|
||||||
let testDir = {
|
|
||||||
'orig_path': {
|
|
||||||
'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)}),
|
|
||||||
'file-2.txt': mockfs.file({content: 'file-2.txt content', mtime: new Date(1000)}),
|
|
||||||
'subdir-1': {
|
|
||||||
'file-1.1.txt': mockfs.file({content: 'file-1.1.txt content', mtime: new Date(1000)})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'symlinks': {
|
|
||||||
'file-1.txt': mockfs.symlink({path: '../orig_path/file-1.txt'}),
|
|
||||||
'file-2.txt': mockfs.symlink({path: '../orig_path/file-2.txt'}),
|
|
||||||
'subdir-1':
|
|
||||||
{'file-1.1.txt': mockfs.symlink({path: '../../orig_path/subdir-1/file-1.1.txt'})}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let differ = new TreeDiffer('testLabel', 'symlinks');
|
|
||||||
|
|
||||||
let diffResult = differ.diffTree();
|
|
||||||
|
|
||||||
expect(diffResult.addedPaths).toEqual([
|
|
||||||
'file-1.txt', 'file-2.txt', 'subdir-1' + path.sep + 'file-1.1.txt'
|
|
||||||
]);
|
|
||||||
|
|
||||||
// change two files
|
|
||||||
testDir['orig_path']['file-1.txt'] =
|
|
||||||
mockfs.file({content: 'new content', mtime: new Date(1000)});
|
|
||||||
testDir['orig_path']['subdir-1']['file-1.1.txt'] =
|
|
||||||
mockfs.file({content: 'file-1.1.txt content', mtime: new Date(9999)});
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
diffResult = differ.diffTree();
|
|
||||||
|
|
||||||
expect(diffResult.addedPaths).toEqual([]);
|
|
||||||
expect(diffResult.changedPaths).toEqual([
|
|
||||||
'file-1.txt', 'subdir-1' + path.sep + 'file-1.1.txt'
|
|
||||||
]);
|
|
||||||
expect(diffResult.removedPaths).toEqual([]);
|
|
||||||
|
|
||||||
// change one file
|
|
||||||
testDir['orig_path']['file-1.txt'] =
|
|
||||||
mockfs.file({content: 'super new', mtime: new Date(1000)});
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
diffResult = differ.diffTree();
|
|
||||||
expect(diffResult.changedPaths).toEqual(['file-1.txt']);
|
|
||||||
|
|
||||||
// remove a link
|
|
||||||
delete testDir['orig_path']['file-1.txt'];
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
diffResult = differ.diffTree();
|
|
||||||
expect(diffResult.addedPaths).toEqual([]);
|
|
||||||
expect(diffResult.changedPaths).toEqual([]);
|
|
||||||
expect(diffResult.removedPaths).toEqual(['file-1.txt']);
|
|
||||||
|
|
||||||
// don't report it as a removal twice
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
diffResult = differ.diffTree();
|
|
||||||
expect(diffResult.changedPaths).toEqual([]);
|
|
||||||
expect(diffResult.removedPaths).toEqual([]);
|
|
||||||
|
|
||||||
// re-add it.
|
|
||||||
testDir['orig_path']['file-1.txt'] =
|
|
||||||
mockfs.file({content: 'super new', mtime: new Date(1000)});
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
diffResult = differ.diffTree();
|
|
||||||
expect(diffResult.addedPaths).toEqual(['file-1.txt']);
|
|
||||||
expect(diffResult.changedPaths).toEqual([]);
|
|
||||||
expect(diffResult.removedPaths).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should throw an error if an extension isn\'t prefixed with doc', () => {
|
|
||||||
// includeExtensions
|
|
||||||
expect(() => new TreeDiffer('testLabel', 'dir1', ['js']))
|
|
||||||
.toThrowError('Extension must begin with \'.\'. Was: \'js\'');
|
|
||||||
|
|
||||||
// excludeExtentions
|
|
||||||
expect(() => new TreeDiffer('testLabel', 'dir1', [], ['js']))
|
|
||||||
.toThrowError('Extension must begin with \'.\'. Was: \'js\'');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should ignore files with extensions not listed in includeExtensions', () => {
|
|
||||||
let testDir = {
|
|
||||||
'dir1': {
|
|
||||||
'file-1.js': mockfs.file({content: 'file-1.js content', mtime: new Date(1000)}),
|
|
||||||
'file-2.md': mockfs.file({content: 'file-2.md content', mtime: new Date(1000)}),
|
|
||||||
'file-3.coffee': mockfs.file({content: 'file-3.coffee content', mtime: new Date(1000)}),
|
|
||||||
'subdir-1': {
|
|
||||||
'file-1.1.cc': mockfs.file({content: 'file-1.1.cc content', mtime: new Date(1000)})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let differ = new TreeDiffer('testLabel', 'dir1', ['.js', '.coffee']);
|
|
||||||
|
|
||||||
let diffResult = differ.diffTree();
|
|
||||||
|
|
||||||
expect(diffResult.addedPaths).toEqual(['file-1.js', 'file-3.coffee']);
|
|
||||||
expect(diffResult.changedPaths).toEqual([]);
|
|
||||||
expect(diffResult.removedPaths).toEqual([]);
|
|
||||||
|
|
||||||
// change two files
|
|
||||||
testDir['dir1']['file-1.js'] = mockfs.file({content: 'new content', mtime: new Date(1000)});
|
|
||||||
testDir['dir1']['file-3.coffee'] =
|
|
||||||
mockfs.file({content: 'new content', mtime: new Date(1000)});
|
|
||||||
testDir['dir1']['subdir-1']['file-1.1.cc'] =
|
|
||||||
mockfs.file({content: 'file-1.1.cc content', mtime: new Date(9999)});
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
diffResult = differ.diffTree();
|
|
||||||
|
|
||||||
expect(diffResult.addedPaths).toEqual([]);
|
|
||||||
expect(diffResult.changedPaths).toEqual(['file-1.js', 'file-3.coffee']);
|
|
||||||
expect(diffResult.removedPaths).toEqual([]);
|
|
||||||
|
|
||||||
// change one file
|
|
||||||
testDir['dir1']['file-1.js'] = mockfs.file({content: 'super new', mtime: new Date(1000)});
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
diffResult = differ.diffTree();
|
|
||||||
expect(diffResult.changedPaths).toEqual(['file-1.js']);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should ignore files with extensions listed in excludeExtensions', () => {
|
|
||||||
let testDir = {
|
|
||||||
'dir1': {
|
|
||||||
'file-1.ts': mockfs.file({content: 'file-1.ts content', mtime: new Date(1000)}),
|
|
||||||
'file-1.cs': mockfs.file({content: 'file-1.cs content', mtime: new Date(1000)}),
|
|
||||||
'file-1d.cs': mockfs.file({content: 'file-1d.cs content', mtime: new Date(1000)}),
|
|
||||||
'file-1.d.cs': mockfs.file({content: 'file-1.d.cs content', mtime: new Date(1000)}),
|
|
||||||
'file-2.md': mockfs.file({content: 'file-2.md content', mtime: new Date(1000)}),
|
|
||||||
'file-3.ts': mockfs.file({content: 'file-3.ts content', mtime: new Date(1000)}),
|
|
||||||
'file-4.d.ts': mockfs.file({content: 'file-4.d.ts content', mtime: new Date(1000)}),
|
|
||||||
'subdir-1': {
|
|
||||||
'file-1.1.cc': mockfs.file({content: 'file-1.1.cc content', mtime: new Date(1000)})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let differ = new TreeDiffer('testLabel', 'dir1', ['.ts', '.cs'], ['.d.ts', '.d.cs']);
|
|
||||||
|
|
||||||
let diffResult = differ.diffTree();
|
|
||||||
|
|
||||||
expect(diffResult.addedPaths).toEqual(['file-1.cs', 'file-1.ts', 'file-1d.cs', 'file-3.ts']);
|
|
||||||
|
|
||||||
// change two files
|
|
||||||
testDir['dir1']['file-1.ts'] = mockfs.file({content: 'new content', mtime: new Date(1000)});
|
|
||||||
testDir['dir1']['file-1.cs'] = mockfs.file({content: 'new content', mtime: new Date(1000)});
|
|
||||||
testDir['dir1']['file-1.d.cs'] = mockfs.file({content: 'new content', mtime: new Date(1000)});
|
|
||||||
testDir['dir1']['file-3.ts'] = mockfs.file({content: 'new content', mtime: new Date(1000)});
|
|
||||||
testDir['dir1']['file-4.d.ts'] = mockfs.file({content: 'new content', mtime: new Date(1000)});
|
|
||||||
testDir['dir1']['subdir-1']['file-1.1.cc'] =
|
|
||||||
mockfs.file({content: 'file-1.1.cc content', mtime: new Date(9999)});
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
diffResult = differ.diffTree();
|
|
||||||
|
|
||||||
expect(diffResult.addedPaths).toEqual([]);
|
|
||||||
expect(diffResult.changedPaths).toEqual(['file-1.cs', 'file-1.ts', 'file-3.ts']);
|
|
||||||
expect(diffResult.removedPaths).toEqual([]);
|
|
||||||
|
|
||||||
// change one file
|
|
||||||
testDir['dir1']['file-4.d.ts'] = mockfs.file({content: 'super new', mtime: new Date(1000)});
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
diffResult = differ.diffTree();
|
|
||||||
expect(diffResult.changedPaths).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('diff of new files', () => {
|
|
||||||
|
|
||||||
it('should detect file additions', () => {
|
|
||||||
let testDir: any = {
|
|
||||||
'dir1':
|
|
||||||
{'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)})}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let differ = new TreeDiffer('testLabel', 'dir1');
|
|
||||||
differ.diffTree();
|
|
||||||
|
|
||||||
testDir['dir1']['file-2.txt'] = 'new file';
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let diffResult = differ.diffTree();
|
|
||||||
expect(diffResult.addedPaths).toEqual(['file-2.txt']);
|
|
||||||
expect(diffResult.changedPaths).toEqual([]);
|
|
||||||
expect(diffResult.removedPaths).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should detect file additions mixed with file changes', () => {
|
|
||||||
let testDir: any = {
|
|
||||||
'dir1':
|
|
||||||
{'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)})}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let differ = new TreeDiffer('testLabel', 'dir1');
|
|
||||||
differ.diffTree();
|
|
||||||
|
|
||||||
testDir['dir1']['file-1.txt'] = 'new content';
|
|
||||||
testDir['dir1']['file-2.txt'] = 'new file';
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let diffResult = differ.diffTree();
|
|
||||||
expect(diffResult.addedPaths).toEqual(['file-2.txt']);
|
|
||||||
expect(diffResult.changedPaths).toEqual(['file-1.txt']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe('diff of removed files', () => {
|
|
||||||
|
|
||||||
it('should detect file removals and report them as removed files', () => {
|
|
||||||
let testDir = {
|
|
||||||
'dir1':
|
|
||||||
{'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)})}
|
|
||||||
};
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let differ = new TreeDiffer('testLabel', 'dir1');
|
|
||||||
differ.diffTree();
|
|
||||||
|
|
||||||
delete testDir['dir1']['file-1.txt'];
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let diffResult = differ.diffTree();
|
|
||||||
expect(diffResult.changedPaths).toEqual([]);
|
|
||||||
expect(diffResult.removedPaths).toEqual(['file-1.txt']);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should detect file removals mixed with file changes and additions', () => {
|
|
||||||
let testDir: any = {
|
|
||||||
'dir1': {
|
|
||||||
'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)}),
|
|
||||||
'file-2.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let differ = new TreeDiffer('testLabel', 'dir1');
|
|
||||||
differ.diffTree();
|
|
||||||
|
|
||||||
testDir['dir1']['file-1.txt'] = 'changed content';
|
|
||||||
delete testDir['dir1']['file-2.txt'];
|
|
||||||
testDir['dir1']['file-3.txt'] = 'new content';
|
|
||||||
mockfs(testDir);
|
|
||||||
|
|
||||||
let diffResult = differ.diffTree();
|
|
||||||
expect(diffResult.addedPaths).toEqual(['file-3.txt']);
|
|
||||||
expect(diffResult.changedPaths).toEqual(['file-1.txt']);
|
|
||||||
expect(diffResult.removedPaths).toEqual(['file-2.txt']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,175 +0,0 @@
|
||||||
import fs = require('fs');
|
|
||||||
import path = require('path');
|
|
||||||
|
|
||||||
|
|
||||||
function tryStatSync(path: string) {
|
|
||||||
try {
|
|
||||||
return fs.statSync(path);
|
|
||||||
} catch (e) {
|
|
||||||
if (e.code === 'ENOENT') return null;
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class TreeDiffer {
|
|
||||||
private fingerprints: {[key: string]: string} = Object.create(null);
|
|
||||||
private nextFingerprints: {[key: string]: string} = Object.create(null);
|
|
||||||
private rootDirName: string;
|
|
||||||
private include: RegExp = null;
|
|
||||||
private exclude: RegExp = null;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private label: string, private rootPath: string, includeExtensions?: string[],
|
|
||||||
excludeExtensions?: string[]) {
|
|
||||||
this.rootDirName = path.basename(rootPath);
|
|
||||||
|
|
||||||
let buildRegexp = (arr: string[]) => new RegExp(`(${arr.reduce(combine, "")})$`, 'i');
|
|
||||||
|
|
||||||
this.include = (includeExtensions || []).length ? buildRegexp(includeExtensions) : null;
|
|
||||||
this.exclude = (excludeExtensions || []).length ? buildRegexp(excludeExtensions) : null;
|
|
||||||
|
|
||||||
function combine(prev: string, curr: string) {
|
|
||||||
if (curr.charAt(0) !== '.') {
|
|
||||||
throw new Error(`Extension must begin with '.'. Was: '${curr}'`);
|
|
||||||
}
|
|
||||||
let kSpecialRegexpChars = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g;
|
|
||||||
curr = '(' + curr.replace(kSpecialRegexpChars, '\\$&') + ')';
|
|
||||||
return prev ? (prev + '|' + curr) : curr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public diffTree(): DiffResult {
|
|
||||||
let result = new DirtyCheckingDiffResult(this.label, this.rootDirName);
|
|
||||||
this.dirtyCheckPath(this.rootPath, result);
|
|
||||||
this.detectDeletionsAndUpdateFingerprints(result);
|
|
||||||
result.endTime = Date.now();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private dirtyCheckPath(rootDir: string, result: DirtyCheckingDiffResult) {
|
|
||||||
fs.readdirSync(rootDir).forEach((segment) => {
|
|
||||||
let absolutePath = path.join(rootDir, segment);
|
|
||||||
let pathStat = fs.lstatSync(absolutePath);
|
|
||||||
if (pathStat.isSymbolicLink()) {
|
|
||||||
pathStat = tryStatSync(absolutePath);
|
|
||||||
if (pathStat === null) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pathStat.isDirectory()) {
|
|
||||||
result.directoriesChecked++;
|
|
||||||
this.dirtyCheckPath(absolutePath, result);
|
|
||||||
} else {
|
|
||||||
if (!(this.include && !absolutePath.match(this.include)) &&
|
|
||||||
!(this.exclude && absolutePath.match(this.exclude))) {
|
|
||||||
result.filesChecked++;
|
|
||||||
let relativeFilePath = path.relative(this.rootPath, absolutePath);
|
|
||||||
|
|
||||||
switch (this.isFileDirty(absolutePath, pathStat)) {
|
|
||||||
case FileStatus.Added:
|
|
||||||
result.addedPaths.push(relativeFilePath);
|
|
||||||
break;
|
|
||||||
case FileStatus.Changed:
|
|
||||||
result.changedPaths.push(relativeFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private isFileDirty(path: string, stat: fs.Stats): FileStatus {
|
|
||||||
let oldFingerprint = this.fingerprints[path];
|
|
||||||
let newFingerprint = `${stat.mtime.getTime()} # ${stat.size}`;
|
|
||||||
|
|
||||||
this.nextFingerprints[path] = newFingerprint;
|
|
||||||
|
|
||||||
if (oldFingerprint) {
|
|
||||||
this.fingerprints[path] = null;
|
|
||||||
|
|
||||||
if (oldFingerprint === newFingerprint) {
|
|
||||||
// nothing changed
|
|
||||||
return FileStatus.Unchanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
return FileStatus.Changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
return FileStatus.Added;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private detectDeletionsAndUpdateFingerprints(result: DiffResult) {
|
|
||||||
for (let absolutePath in this.fingerprints) {
|
|
||||||
if (!(this.include && !absolutePath.match(this.include)) &&
|
|
||||||
!(this.exclude && absolutePath.match(this.exclude))) {
|
|
||||||
if (this.fingerprints[absolutePath] !== null) {
|
|
||||||
let relativePath = path.relative(this.rootPath, absolutePath);
|
|
||||||
result.removedPaths.push(relativePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fingerprints = this.nextFingerprints;
|
|
||||||
this.nextFingerprints = Object.create(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class DiffResult {
|
|
||||||
public addedPaths: string[] = [];
|
|
||||||
public changedPaths: string[] = [];
|
|
||||||
public removedPaths: string[] = [];
|
|
||||||
|
|
||||||
constructor(public label: string = '') {}
|
|
||||||
|
|
||||||
log(verbose: boolean): void {}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
// TODO(@caitp): more meaningful logging
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DirtyCheckingDiffResult extends DiffResult {
|
|
||||||
public filesChecked: number = 0;
|
|
||||||
public directoriesChecked: number = 0;
|
|
||||||
public startTime: number = Date.now();
|
|
||||||
public endTime: number = null;
|
|
||||||
|
|
||||||
constructor(label: string, public directoryName: string) { super(label); }
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
return `${pad(this.label, 30)}, ${pad(this.endTime - this.startTime, 5)}ms, ` +
|
|
||||||
`${pad(this.addedPaths.length + this.changedPaths.length + this.removedPaths.length, 5)} changes ` +
|
|
||||||
`(files: ${pad(this.filesChecked, 5)}, dirs: ${pad(this.directoriesChecked, 4)})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
log(verbose: boolean) {
|
|
||||||
let prefixedPaths = this.addedPaths.map(p => `+ ${p}`)
|
|
||||||
.concat(this.changedPaths.map(p => `* ${p}`))
|
|
||||||
.concat(this.removedPaths.map(p => `- ${p}`));
|
|
||||||
console.log(
|
|
||||||
`Tree diff: ${this}` +
|
|
||||||
((verbose && prefixedPaths.length) ? ` [\n ${prefixedPaths.join('\n ')}\n]` : ''));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function pad(v: string | number, length: number) {
|
|
||||||
let value = '' + v;
|
|
||||||
let whitespaceLength = (value.length < length) ? length - value.length : 0;
|
|
||||||
whitespaceLength = whitespaceLength + 1;
|
|
||||||
return new Array(whitespaceLength).join(' ') + value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
enum FileStatus {
|
|
||||||
Added,
|
|
||||||
Unchanged,
|
|
||||||
Changed
|
|
||||||
}
|
|
|
@ -1,325 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var Funnel = require('broccoli-funnel');
|
|
||||||
var htmlReplace = require('../html-replace');
|
|
||||||
var jsReplace = require('../js-replace');
|
|
||||||
var path = require('path');
|
|
||||||
var stew = require('broccoli-stew');
|
|
||||||
|
|
||||||
import compileWithTypescript from '../broccoli-typescript';
|
|
||||||
import destCopy from '../broccoli-dest-copy';
|
|
||||||
import flatten from '../broccoli-flatten';
|
|
||||||
import mergeTrees from '../broccoli-merge-trees';
|
|
||||||
import replace from '../broccoli-replace';
|
|
||||||
import checkImports from '../broccoli-check-imports';
|
|
||||||
|
|
||||||
|
|
||||||
const kServedPaths = [
|
|
||||||
// Relative (to /modules) paths to benchmark directories
|
|
||||||
'benchmarks/src',
|
|
||||||
'benchmarks/src/change_detection',
|
|
||||||
'benchmarks/src/compiler',
|
|
||||||
'benchmarks/src/costs',
|
|
||||||
'benchmarks/src/di',
|
|
||||||
'benchmarks/src/element_injector',
|
|
||||||
'benchmarks/src/largetable',
|
|
||||||
'benchmarks/src/naive_infinite_scroll',
|
|
||||||
'benchmarks/src/page_load',
|
|
||||||
'benchmarks/src/tree',
|
|
||||||
'benchmarks/src/static_tree',
|
|
||||||
|
|
||||||
// Relative (to /modules) paths to external benchmark directories
|
|
||||||
'benchmarks_external/src',
|
|
||||||
'benchmarks_external/src/compiler',
|
|
||||||
'benchmarks_external/src/largetable',
|
|
||||||
'benchmarks_external/src/naive_infinite_scroll',
|
|
||||||
'benchmarks_external/src/tree',
|
|
||||||
'benchmarks_external/src/tree/react',
|
|
||||||
'benchmarks_external/src/static_tree',
|
|
||||||
|
|
||||||
// Relative (to /modules) paths to example directories
|
|
||||||
'playground/src/animate',
|
|
||||||
'playground/src/benchpress',
|
|
||||||
'playground/src/model_driven_forms',
|
|
||||||
'playground/src/template_driven_forms',
|
|
||||||
'playground/src/person_management',
|
|
||||||
'playground/src/order_management',
|
|
||||||
'playground/src/gestures',
|
|
||||||
'playground/src/hash_routing',
|
|
||||||
'playground/src/hello_world',
|
|
||||||
'playground/src/http',
|
|
||||||
'playground/src/jsonp',
|
|
||||||
'playground/src/key_events',
|
|
||||||
'playground/src/relative_assets',
|
|
||||||
'playground/src/routing',
|
|
||||||
'playground/src/sourcemap',
|
|
||||||
'playground/src/svg',
|
|
||||||
'playground/src/todo',
|
|
||||||
'playground/src/upgrade',
|
|
||||||
'playground/src/zippy_component',
|
|
||||||
'playground/src/async',
|
|
||||||
'playground/src/web_workers/kitchen_sink',
|
|
||||||
'playground/src/web_workers/todo',
|
|
||||||
'playground/src/web_workers/images',
|
|
||||||
'playground/src/web_workers/message_broker',
|
|
||||||
'playground/src/web_workers/router',
|
|
||||||
'playground/src/web_workers/input',
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = function makeBrowserTree(options: any, destinationPath: string) {
|
|
||||||
// TODO: define an interface for the options
|
|
||||||
const modules = options.projects;
|
|
||||||
const noTypeChecks = options.noTypeChecks;
|
|
||||||
const generateEs6 = options.generateEs6;
|
|
||||||
const sourceMaps = options.sourceMaps;
|
|
||||||
const useBundles = options.useBundles;
|
|
||||||
|
|
||||||
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.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.payload_tests) {
|
|
||||||
var payloadTestsTree = new Funnel(
|
|
||||||
'modules/payload_tests',
|
|
||||||
{include: ['**/ts/**'], exclude: ['e2e_test/**'], destDir: '/payload_tests/'});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modules.playground) {
|
|
||||||
var playgroundTree = new Funnel(
|
|
||||||
'modules/playground',
|
|
||||||
{include: ['**/**'], exclude: ['e2e_test/**'], destDir: '/playground/'});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modules.benchpress) {
|
|
||||||
var benchpressTree = new Funnel(
|
|
||||||
'modules/benchpress',
|
|
||||||
{include: ['**/**'], exclude: ['e2e_test/**'], destDir: '/benchpress/'});
|
|
||||||
}
|
|
||||||
|
|
||||||
let externalTypings =
|
|
||||||
new Funnel('node_modules', {include: ['rxjs/**/*.d.ts', 'zone.js/**/*.d.ts']});
|
|
||||||
|
|
||||||
|
|
||||||
var modulesTree = mergeTrees([
|
|
||||||
angular2Tree,
|
|
||||||
benchmarksTree,
|
|
||||||
benchmarksExternalTree,
|
|
||||||
payloadTestsTree,
|
|
||||||
playgroundTree,
|
|
||||||
benchpressTree,
|
|
||||||
externalTypings,
|
|
||||||
]);
|
|
||||||
|
|
||||||
var es6PolyfillTypings =
|
|
||||||
new Funnel('modules', {include: ['angular2/typings/es6-*/**'], destDir: '/'});
|
|
||||||
|
|
||||||
var es5ModulesTree = mergeTrees([modulesTree, es6PolyfillTypings]);
|
|
||||||
|
|
||||||
var scriptPathPatternReplacement = {
|
|
||||||
match: '@@PATH',
|
|
||||||
replacement: function(replacement: string, relativePath: string) {
|
|
||||||
var parts = relativePath.replace(/\\/g, '/').split('/');
|
|
||||||
return parts.splice(0, parts.length - 1).join('/');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var scriptFilePatternReplacement = {
|
|
||||||
match: '@@FILENAME',
|
|
||||||
replacement: function(replacement: string, relativePath: string) {
|
|
||||||
var parts = relativePath.replace(/\\/g, '/').split('/');
|
|
||||||
return parts[parts.length - 1].replace('html', 'js');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var useBundlesPatternReplacement = {
|
|
||||||
match: '@@USE_BUNDLES',
|
|
||||||
replacement: function(replacement: string, relativePath: string) { return useBundles; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check that imports do not break barrel boundaries
|
|
||||||
modulesTree = checkImports(modulesTree);
|
|
||||||
|
|
||||||
modulesTree = replace(modulesTree, {
|
|
||||||
files: ['playground*/**/*.js'],
|
|
||||||
patterns: [{match: /\$SCRIPTS\$/, replacement: jsReplace('SCRIPTS')}]
|
|
||||||
});
|
|
||||||
|
|
||||||
let ambientTypings = [
|
|
||||||
'angular2/typings/hammerjs/hammerjs.d.ts',
|
|
||||||
'angular2/typings/node/node.d.ts',
|
|
||||||
'node_modules/zone.js/dist/zone.js.d.ts',
|
|
||||||
'angular2/manual_typings/globals.d.ts',
|
|
||||||
'angular2/typings/es6-collections/es6-collections.d.ts',
|
|
||||||
'angular2/typings/es6-promise/es6-promise.d.ts',
|
|
||||||
];
|
|
||||||
|
|
||||||
// Use TypeScript to transpile the *.ts files to ES5
|
|
||||||
var es5Tree = compileWithTypescript(es5ModulesTree, {
|
|
||||||
declaration: false,
|
|
||||||
emitDecoratorMetadata: true,
|
|
||||||
experimentalDecorators: true,
|
|
||||||
module: 'commonjs',
|
|
||||||
moduleResolution: 'classic',
|
|
||||||
noEmitOnError: !noTypeChecks,
|
|
||||||
rootDir: './',
|
|
||||||
rootFilePaths: ambientTypings,
|
|
||||||
inlineSourceMap: sourceMaps,
|
|
||||||
inlineSources: sourceMaps,
|
|
||||||
target: 'es5'
|
|
||||||
});
|
|
||||||
|
|
||||||
var vendorScriptsTree = flatten(new Funnel('.', {
|
|
||||||
files: [
|
|
||||||
'node_modules/es6-shim/es6-shim.js',
|
|
||||||
'node_modules/zone.js/dist/zone.js',
|
|
||||||
'node_modules/zone.js/dist/long-stack-trace-zone.js',
|
|
||||||
'node_modules/systemjs/dist/system.src.js',
|
|
||||||
'node_modules/base64-js/lib/b64.js',
|
|
||||||
'node_modules/reflect-metadata/Reflect.js',
|
|
||||||
]
|
|
||||||
}));
|
|
||||||
|
|
||||||
var vendorScripts_benchmark =
|
|
||||||
new Funnel('tools/build/snippets', {files: ['url_params_to_form.js'], destDir: '/'});
|
|
||||||
var vendorScripts_benchmarks_external =
|
|
||||||
new Funnel('node_modules/angular', {files: ['angular.js'], destDir: '/'});
|
|
||||||
|
|
||||||
// Get scripts for each benchmark or example
|
|
||||||
let servingTrees = kServedPaths.reduce(getServedFunnels, []);
|
|
||||||
function getServedFunnels(funnels: BroccoliTree[], destDir: string) {
|
|
||||||
let options = {srcDir: '/', destDir: destDir};
|
|
||||||
funnels.push(new Funnel(vendorScriptsTree, options));
|
|
||||||
if (destDir.indexOf('benchmarks') > -1) {
|
|
||||||
funnels.push(new Funnel(vendorScripts_benchmark, options));
|
|
||||||
}
|
|
||||||
if (destDir.indexOf('benchmarks_external') > -1) {
|
|
||||||
funnels.push(new Funnel(vendorScripts_benchmarks_external, options));
|
|
||||||
}
|
|
||||||
return funnels;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (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', '**/payload_tests/**/ts/**/*.html'],
|
|
||||||
destDir: '/'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (modules.playground) {
|
|
||||||
htmlTree = replace(htmlTree, {
|
|
||||||
files: ['playground*/**/*.html'],
|
|
||||||
patterns: [
|
|
||||||
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS')}, scriptPathPatternReplacement,
|
|
||||||
scriptFilePatternReplacement, useBundlesPatternReplacement
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modules.benchmarks) {
|
|
||||||
htmlTree = replace(htmlTree, {
|
|
||||||
files: ['benchmarks/**'],
|
|
||||||
patterns: [
|
|
||||||
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS_benchmarks')},
|
|
||||||
scriptPathPatternReplacement, scriptFilePatternReplacement, useBundlesPatternReplacement
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modules.benchmarks_external) {
|
|
||||||
htmlTree = replace(htmlTree, {
|
|
||||||
files: ['benchmarks_external/**'],
|
|
||||||
patterns: [
|
|
||||||
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS_benchmarks_external')},
|
|
||||||
scriptPathPatternReplacement, scriptFilePatternReplacement, useBundlesPatternReplacement
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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'}]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modules.benchmarks || modules.benchmarks_external) {
|
|
||||||
var scripts = mergeTrees(servingTrees);
|
|
||||||
}
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modules.benchmarks || modules.benchmarks_external || modules.playground) {
|
|
||||||
htmlTree = mergeTrees([htmlTree, scripts, polymer, react]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is needed only for creating a bundle
|
|
||||||
// typescript resolves dependencies automatically
|
|
||||||
if (modules.bundle_deps) {
|
|
||||||
var nodeModules = new Funnel(
|
|
||||||
'node_modules', {include: ['rxjs/**/**', 'parse5/**/**', 'css/**/**'], destDir: '/'});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (generateEs6) {
|
|
||||||
// Use TypeScript to transpile the *.ts files to ES6
|
|
||||||
var es6Tree = compileWithTypescript(modulesTree, {
|
|
||||||
declaration: false,
|
|
||||||
emitDecoratorMetadata: true,
|
|
||||||
experimentalDecorators: true,
|
|
||||||
noEmitOnError: false,
|
|
||||||
rootDir: './',
|
|
||||||
rootFilePaths: [
|
|
||||||
'angular2/typings/zone.js/zone.js.d.ts',
|
|
||||||
'angular2/typings/hammerjs/hammerjs.d.ts',
|
|
||||||
'angular2/typings/node/node.d.ts',
|
|
||||||
],
|
|
||||||
inlineSourceMap: sourceMaps,
|
|
||||||
inlineSources: sourceMaps,
|
|
||||||
target: 'es6'
|
|
||||||
});
|
|
||||||
|
|
||||||
es6Tree = stew.mv(mergeTrees([es6Tree, htmlTree, assetsTree, nodeModules]), '/es6');
|
|
||||||
}
|
|
||||||
es5Tree = stew.mv(mergeTrees([es5Tree, htmlTree, assetsTree, nodeModules]), '/es5');
|
|
||||||
|
|
||||||
var mergedTree = mergeTrees([es6Tree, es5Tree]);
|
|
||||||
return destCopy(mergedTree, destinationPath);
|
|
||||||
};
|
|
|
@ -1,213 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import {MultiCopy} from './../multi_copy';
|
|
||||||
import destCopy from '../broccoli-dest-copy';
|
|
||||||
var Funnel = require('broccoli-funnel');
|
|
||||||
import mergeTrees from '../broccoli-merge-trees';
|
|
||||||
var path = require('path');
|
|
||||||
import renderLodashTemplate from '../broccoli-lodash';
|
|
||||||
var stew = require('broccoli-stew');
|
|
||||||
import ts2dart from '../broccoli-ts2dart';
|
|
||||||
import dartfmt from '../broccoli-dartfmt';
|
|
||||||
import replace from '../broccoli-replace';
|
|
||||||
import {AngularBuilderOptions} from '../angular_builder';
|
|
||||||
import generateForTest from '../broccoli-generate-for-test';
|
|
||||||
|
|
||||||
var global_excludes = [
|
|
||||||
'angular2/examples/**/ts/**/*',
|
|
||||||
'angular2/http*',
|
|
||||||
'angular2/http/**/*',
|
|
||||||
'angular2/src/http/**/*',
|
|
||||||
'angular2/src/upgrade/**/*',
|
|
||||||
'angular2/test/http/**/*',
|
|
||||||
'angular2/test/upgrade/**/*',
|
|
||||||
'angular2/upgrade*',
|
|
||||||
'payload_tests/**/ts/**/*',
|
|
||||||
'playground/src/http/**/*',
|
|
||||||
'playground/src/jsonp/**/*',
|
|
||||||
'playground/test/http/**/*',
|
|
||||||
'playground/test/jsonp/**/*',
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A funnel starting at modules, including the given filters, and moving into the root.
|
|
||||||
* @param include Include glob filters.
|
|
||||||
*/
|
|
||||||
function modulesFunnel(include: string[], exclude?: string[]) {
|
|
||||||
exclude = exclude || [];
|
|
||||||
exclude = exclude.concat(global_excludes);
|
|
||||||
return new Funnel('modules', {include, destDir: '/', exclude});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces $SCRIPT$ in .html files with actual <script> tags.
|
|
||||||
*/
|
|
||||||
function replaceScriptTagInHtml(placeholder: string, relativePath: string): string {
|
|
||||||
var scriptTags = '';
|
|
||||||
if (relativePath.match(/^benchmarks/)) {
|
|
||||||
scriptTags += '<script src="url_params_to_form.js" type="text/javascript"></script>\n';
|
|
||||||
}
|
|
||||||
var scriptName = relativePath.replace(/\\/g, '/').replace(/.*\/([^/]+)\.html$/, '$1.dart');
|
|
||||||
scriptTags += '<script src="' + scriptName + '" type="application/dart"></script>\n' +
|
|
||||||
'<script src="packages/browser/dart.js" type="text/javascript"></script>';
|
|
||||||
return scriptTags;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripModulePrefix(relativePath: string): string {
|
|
||||||
if (!relativePath.match(/^modules\//)) {
|
|
||||||
throw new Error('Expected path to root at modules/: ' + relativePath);
|
|
||||||
}
|
|
||||||
return relativePath.replace(/^modules\//, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSourceTree(options: AngularBuilderOptions) {
|
|
||||||
var tsInputTree = modulesFunnel(
|
|
||||||
[
|
|
||||||
'tsconfig-ts2dart.json',
|
|
||||||
'upgrade-ts2dart.d.ts',
|
|
||||||
'zone-ts2dart.d.ts',
|
|
||||||
'**/*.js',
|
|
||||||
'**/*.ts',
|
|
||||||
'**/*.dart',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'rollup-test/**/*',
|
|
||||||
'angular1_router/**/*',
|
|
||||||
'angular2/upgrade/**/*',
|
|
||||||
'angular2/core/test/typings.d.ts',
|
|
||||||
'angular2/manual_typings/globals.d.ts',
|
|
||||||
'angular2/typings/es6-collections/es6-collections.d.ts',
|
|
||||||
'angular2/typings/es6-promise/es6-promise.d.ts',
|
|
||||||
'angular2/typings/tsd.d.ts',
|
|
||||||
'angular2/typings.d.ts',
|
|
||||||
]);
|
|
||||||
var transpiled = ts2dart(tsInputTree, {
|
|
||||||
generateLibraryName: true,
|
|
||||||
generateSourceMap: false,
|
|
||||||
translateBuiltins: true,
|
|
||||||
tsconfig: 'tsconfig-ts2dart.json'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Native sources, dart only examples, etc.
|
|
||||||
var dartSrcs = modulesFunnel(
|
|
||||||
['**/*.dart', '**/*.ng_meta.json', '**/*.aliases.json', '**/css/**', '**/*.css']);
|
|
||||||
|
|
||||||
var compiledTree = mergeTrees([transpiled, dartSrcs]);
|
|
||||||
|
|
||||||
// Generate test files
|
|
||||||
let generatedDartTestFiles = generateForTest(
|
|
||||||
mergeTrees([compiledTree, new Funnel('packages', {include: ['path/**', 'stack_trace/**']})]),
|
|
||||||
{files: ['*/test/**/*_codegen_typed.dart'], dartPath: options.dartSDK.VM});
|
|
||||||
|
|
||||||
return mergeTrees([compiledTree, generatedDartTestFiles], {overwrite: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
function fixDartFolderLayout(sourceTree: BroccoliTree) {
|
|
||||||
// Move around files to match Dart's layout expectations.
|
|
||||||
return stew.rename(sourceTree, function(relativePath: string) {
|
|
||||||
// If a file matches the `pattern`, insert the given `insertion` as the second path part.
|
|
||||||
var replacements = [
|
|
||||||
{pattern: /^benchmarks\/test\//, insertion: ''},
|
|
||||||
{pattern: /^benchmarks\//, insertion: 'web'},
|
|
||||||
{pattern: /^benchmarks_external\/test\//, insertion: ''},
|
|
||||||
{pattern: /^benchmarks_external\//, insertion: 'web'},
|
|
||||||
{pattern: /^playground\/test\//, insertion: ''},
|
|
||||||
{pattern: /^playground\//, insertion: 'web/'},
|
|
||||||
{pattern: /^[^\/]*\/test\//, insertion: ''},
|
|
||||||
// catch all.
|
|
||||||
{pattern: /^./, insertion: 'lib'},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (var i = 0; i < replacements.length; i++) {
|
|
||||||
var repl = replacements[i];
|
|
||||||
if (relativePath.match(repl.pattern)) {
|
|
||||||
var parts = relativePath.split('/');
|
|
||||||
parts.splice(1, 0, repl.insertion);
|
|
||||||
return path.join.apply(path, parts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error('Failed to match any path: ' + relativePath);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHtmlSourcesTree() {
|
|
||||||
// Replace $SCRIPT$ markers in HTML files.
|
|
||||||
var htmlSrcsTree = modulesFunnel(['*/src/**/*.html']);
|
|
||||||
htmlSrcsTree = replace(
|
|
||||||
htmlSrcsTree,
|
|
||||||
{files: ['*/**'], patterns: [{match: '$SCRIPTS$', replacement: replaceScriptTagInHtml}]});
|
|
||||||
|
|
||||||
// Copy a url_params_to_form.js for each benchmark html file.
|
|
||||||
var urlParamsToFormTree = new MultiCopy(<any>'', {
|
|
||||||
srcPath: 'tools/build/snippets/url_params_to_form.js',
|
|
||||||
targetPatterns: ['modules/benchmarks*/src/*', 'modules/benchmarks*/src/*/*'],
|
|
||||||
});
|
|
||||||
urlParamsToFormTree = stew.rename(urlParamsToFormTree, stripModulePrefix);
|
|
||||||
return mergeTrees([htmlSrcsTree, urlParamsToFormTree]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getExamplesJsonTree() {
|
|
||||||
// Copy JSON files
|
|
||||||
return modulesFunnel(['playground/**/*.json']);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getTemplatedPubspecsTree() {
|
|
||||||
// The JSON structure for templating pubspec.yaml files.
|
|
||||||
var BASE_PACKAGE_JSON = require('../../../../package.json');
|
|
||||||
var COMMON_PACKAGE_JSON = {
|
|
||||||
version: BASE_PACKAGE_JSON.version,
|
|
||||||
homepage: BASE_PACKAGE_JSON.homepage,
|
|
||||||
bugs: BASE_PACKAGE_JSON.bugs,
|
|
||||||
license: BASE_PACKAGE_JSON.license,
|
|
||||||
contributors: BASE_PACKAGE_JSON.contributors,
|
|
||||||
dependencies: BASE_PACKAGE_JSON.dependencies,
|
|
||||||
devDependencies: {}
|
|
||||||
};
|
|
||||||
// Generate pubspec.yaml from templates.
|
|
||||||
var pubspecs = modulesFunnel(['**/pubspec.yaml']);
|
|
||||||
// Then render the templates.
|
|
||||||
return renderLodashTemplate(pubspecs, {context: {'packageJson': COMMON_PACKAGE_JSON}});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDocsTree() {
|
|
||||||
// LICENSE files
|
|
||||||
var licenses = new MultiCopy(<any>'', {
|
|
||||||
srcPath: 'LICENSE',
|
|
||||||
targetPatterns: ['modules/*'],
|
|
||||||
exclude: [
|
|
||||||
'*/@angular',
|
|
||||||
'*/angular2',
|
|
||||||
'*/angular1_router',
|
|
||||||
'*/angular2/src/http',
|
|
||||||
'*/payload_tests',
|
|
||||||
'*/upgrade',
|
|
||||||
] // Not in dart.
|
|
||||||
});
|
|
||||||
licenses = stew.rename(licenses, stripModulePrefix);
|
|
||||||
|
|
||||||
// Documentation.
|
|
||||||
// Rename *.dart.md -> *.dart.
|
|
||||||
var mdTree = stew.rename(
|
|
||||||
modulesFunnel(['**/*.dart.md']),
|
|
||||||
(relativePath: string) => relativePath.replace(/\.dart\.md$/, '.md'));
|
|
||||||
// Copy all assets, ignore .js. and .dart. (handled above).
|
|
||||||
var docs = modulesFunnel(
|
|
||||||
['**/*.md', '**/*.png', '**/*.html', '**/*.css', '**/*.scss'],
|
|
||||||
['**/*.js.md', '**/*.dart.md', 'angular1_router/**/*']);
|
|
||||||
|
|
||||||
var assets = modulesFunnel(['playground/**/*.json']);
|
|
||||||
|
|
||||||
return mergeTrees([licenses, mdTree, docs, assets]);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function makeDartTree(options: AngularBuilderOptions) {
|
|
||||||
var dartSources = dartfmt(getSourceTree(options), {dartSDK: options.dartSDK, logs: options.logs});
|
|
||||||
var sourceTree = mergeTrees([dartSources, getHtmlSourcesTree(), getExamplesJsonTree()]);
|
|
||||||
sourceTree = fixDartFolderLayout(sourceTree);
|
|
||||||
|
|
||||||
var dartTree = mergeTrees([sourceTree, getTemplatedPubspecsTree(), getDocsTree()]);
|
|
||||||
|
|
||||||
return destCopy(dartTree, options.outputPath);
|
|
||||||
};
|
|
|
@ -1,255 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import destCopy from '../broccoli-dest-copy';
|
|
||||||
import compileWithTypescript, {INTERNAL_TYPINGS_PATH} from '../broccoli-typescript';
|
|
||||||
var Funnel = require('broccoli-funnel');
|
|
||||||
import mergeTrees from '../broccoli-merge-trees';
|
|
||||||
var path = require('path');
|
|
||||||
import renderLodashTemplate from '../broccoli-lodash';
|
|
||||||
import replace from '../broccoli-replace';
|
|
||||||
import generateForTest from '../broccoli-generate-for-test';
|
|
||||||
var stew = require('broccoli-stew');
|
|
||||||
var writeFile = require('broccoli-file-creator');
|
|
||||||
|
|
||||||
var projectRootDir = path.normalize(path.join(__dirname, '..', '..', '..', '..'));
|
|
||||||
|
|
||||||
module.exports = function makeNodeTree(projects: string[], destinationPath: string) {
|
|
||||||
// list of npm packages that this build will create
|
|
||||||
var outputPackages = ['angular2', 'benchpress'];
|
|
||||||
|
|
||||||
let srcTree = new Funnel('modules', {
|
|
||||||
include: ['angular2/**'],
|
|
||||||
exclude: [
|
|
||||||
'**/e2e_test/**',
|
|
||||||
'angular2/test/**',
|
|
||||||
'angular2/examples/**',
|
|
||||||
|
|
||||||
'angular2/src/testing/**',
|
|
||||||
'angular2/testing.ts',
|
|
||||||
'angular2/testing_internal.ts',
|
|
||||||
'angular2/src/upgrade/**',
|
|
||||||
'angular2/upgrade.ts',
|
|
||||||
'angular2/platform/testing/**',
|
|
||||||
'angular2/manual_typings/**',
|
|
||||||
'angular2/typings/**',
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
let externalTypings = [
|
|
||||||
'angular2/typings/hammerjs/hammerjs.d.ts',
|
|
||||||
'angular2/typings/node/node.d.ts',
|
|
||||||
'angular2/manual_typings/globals.d.ts',
|
|
||||||
'angular2/typings/es6-collections/es6-collections.d.ts',
|
|
||||||
'angular2/typings/es6-promise/es6-promise.d.ts',
|
|
||||||
];
|
|
||||||
|
|
||||||
let externalTypingsTree = new Funnel('modules', {files: externalTypings});
|
|
||||||
|
|
||||||
let packageTypings =
|
|
||||||
new Funnel('node_modules', {include: ['rxjs/**/*.d.ts', 'zone.js/**/*.d.ts']});
|
|
||||||
|
|
||||||
let compileSrcContext = mergeTrees([srcTree, externalTypingsTree, packageTypings]);
|
|
||||||
|
|
||||||
// Compile the sources and generate the @internal .d.ts
|
|
||||||
let compiledSrcTreeWithInternals = compileTree(compileSrcContext, true, []);
|
|
||||||
|
|
||||||
var testTree = new Funnel('modules', {
|
|
||||||
include: [
|
|
||||||
'angular2/manual_typings/**',
|
|
||||||
'angular2/typings/**',
|
|
||||||
|
|
||||||
'angular2/test/**',
|
|
||||||
'benchpress/**',
|
|
||||||
'**/e2e_test/**',
|
|
||||||
'angular2/examples/**/*_spec.ts',
|
|
||||||
|
|
||||||
'angular2/src/testing/**',
|
|
||||||
'angular2/testing.ts',
|
|
||||||
'angular2/testing_internal.ts',
|
|
||||||
'angular2/src/upgrade/**',
|
|
||||||
'angular2/upgrade.ts',
|
|
||||||
'angular2/platform/testing/**',
|
|
||||||
],
|
|
||||||
exclude: [
|
|
||||||
// the following code and tests are not compatible with CJS/node environment
|
|
||||||
'angular2/test/animate/**',
|
|
||||||
'angular2/test/core/zone/**',
|
|
||||||
'angular2/test/testing/fake_async_spec.ts',
|
|
||||||
'angular2/test/testing/testing_public_browser_spec.ts',
|
|
||||||
'angular2/test/platform/xhr_impl_spec.ts',
|
|
||||||
'angular2/test/platform/browser/**/*.ts',
|
|
||||||
'angular2/test/common/forms/**',
|
|
||||||
'angular2/manual_typings/**',
|
|
||||||
'angular2/typings/**',
|
|
||||||
|
|
||||||
// we call browser's bootstrap
|
|
||||||
'angular2/test/router/route_config/route_config_spec.ts',
|
|
||||||
'angular2/test/router/integration/bootstrap_spec.ts',
|
|
||||||
|
|
||||||
// we check the public api by importing angular2/angular2
|
|
||||||
'angular2/test/symbol_inspector/**/*.ts',
|
|
||||||
'angular2/test/public_api_spec.ts',
|
|
||||||
|
|
||||||
'angular2/test/web_workers/worker/renderer_integration_spec.ts',
|
|
||||||
|
|
||||||
'angular2/test/upgrade/**/*.ts',
|
|
||||||
'angular1_router/**',
|
|
||||||
'payload_tests/**',
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Compile the tests against the src @internal .d.ts
|
|
||||||
let srcPrivateDeclarations =
|
|
||||||
new Funnel(compiledSrcTreeWithInternals, {srcDir: INTERNAL_TYPINGS_PATH});
|
|
||||||
|
|
||||||
let testAmbients = [
|
|
||||||
'angular2/typings/jasmine/jasmine.d.ts',
|
|
||||||
'angular2/typings/angular-protractor/angular-protractor.d.ts',
|
|
||||||
'angular2/typings/selenium-webdriver/selenium-webdriver.d.ts'
|
|
||||||
];
|
|
||||||
let testAmbientsTree = new Funnel('modules', {files: testAmbients});
|
|
||||||
|
|
||||||
testTree = mergeTrees(
|
|
||||||
[testTree, srcPrivateDeclarations, testAmbientsTree, externalTypingsTree, packageTypings]);
|
|
||||||
|
|
||||||
let compiledTestTree = compileTree(testTree, false, []);
|
|
||||||
|
|
||||||
// Merge the compiled sources and tests
|
|
||||||
let compiledSrcTree =
|
|
||||||
new Funnel(compiledSrcTreeWithInternals, {exclude: [`${INTERNAL_TYPINGS_PATH}/**`]});
|
|
||||||
|
|
||||||
let compiledTree = mergeTrees([compiledSrcTree, compiledTestTree]);
|
|
||||||
|
|
||||||
// Generate test files
|
|
||||||
let generatedJsTestFiles =
|
|
||||||
generateForTest(compiledTree, {files: ['*/test/**/*_codegen_untyped.js']});
|
|
||||||
let generatedTsTestFiles = stew.rename(
|
|
||||||
generateForTest(compiledTree, {files: ['*/test/**/*_codegen_typed.js']}), /.js$/, '.ts');
|
|
||||||
|
|
||||||
// Compile generated test files against the src @internal .d.ts and the test files
|
|
||||||
compiledTree = mergeTrees(
|
|
||||||
[
|
|
||||||
compiledTree, generatedJsTestFiles,
|
|
||||||
compileTree(
|
|
||||||
new Funnel(
|
|
||||||
mergeTrees([
|
|
||||||
packageTypings,
|
|
||||||
new Funnel(
|
|
||||||
'modules', {include: ['angular2/manual_typings/**', 'angular2/typings/**']}),
|
|
||||||
generatedTsTestFiles, srcPrivateDeclarations, compiledTestTree
|
|
||||||
]),
|
|
||||||
{include: ['angular2/**', 'rxjs/**', 'zone.js/**']}),
|
|
||||||
false, [])
|
|
||||||
],
|
|
||||||
{overwrite: true});
|
|
||||||
|
|
||||||
// Down-level .d.ts files to be TS 1.8 compatible
|
|
||||||
// TODO(alexeagle): this can be removed once we drop support for using Angular 2 with TS 1.8
|
|
||||||
compiledTree = replace(compiledTree, {
|
|
||||||
files: ['**/*.d.ts'],
|
|
||||||
patterns: [
|
|
||||||
// all readonly keywords
|
|
||||||
{match: /^(\s*(static\s+|private\s+)*)readonly\s+/mg, replacement: '$1'},
|
|
||||||
// abstract properties (but not methods or classes)
|
|
||||||
{match: /^(\s+)abstract\s+([^\(\n]*$)/mg, replacement: '$1$2'},
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Now we add the LICENSE file into all the folders that will become npm packages
|
|
||||||
outputPackages.forEach(function(destDir) {
|
|
||||||
var license = new Funnel('.', {files: ['LICENSE'], destDir: destDir});
|
|
||||||
// merge the test tree
|
|
||||||
compiledTree = mergeTrees([compiledTree, license]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get all docs and related assets and prepare them for js build
|
|
||||||
var srcDocs = extractDocs(srcTree);
|
|
||||||
var testDocs = extractDocs(testTree);
|
|
||||||
|
|
||||||
var BASE_PACKAGE_JSON = require(path.join(projectRootDir, 'package.json'));
|
|
||||||
var srcPkgJsons = extractPkgJsons(srcTree, BASE_PACKAGE_JSON);
|
|
||||||
var testPkgJsons = extractPkgJsons(testTree, BASE_PACKAGE_JSON);
|
|
||||||
|
|
||||||
// Copy es6 typings so quickstart doesn't require typings install
|
|
||||||
let typingsTree = mergeTrees([
|
|
||||||
new Funnel('modules', {
|
|
||||||
include: [
|
|
||||||
'angular2/typings/es6-collections/es6-collections.d.ts',
|
|
||||||
'angular2/typings/es6-promise/es6-promise.d.ts',
|
|
||||||
]
|
|
||||||
}),
|
|
||||||
writeFile(
|
|
||||||
'angular2/typings/browser.d.ts', '// Typings needed for compilation with --target=es5\n' +
|
|
||||||
'///<reference path="./es6-collections/es6-collections.d.ts"/>\n' +
|
|
||||||
'///<reference path="./es6-promise/es6-promise.d.ts"/>\n')
|
|
||||||
]);
|
|
||||||
|
|
||||||
var nodeTree =
|
|
||||||
mergeTrees([compiledTree, srcDocs, testDocs, srcPkgJsons, testPkgJsons, typingsTree]);
|
|
||||||
|
|
||||||
// Transform all tests to make them runnable in node
|
|
||||||
nodeTree = replace(nodeTree, {
|
|
||||||
files: ['**/test/**/*_spec.js'],
|
|
||||||
patterns: [
|
|
||||||
{
|
|
||||||
match: /^/,
|
|
||||||
replacement:
|
|
||||||
() =>
|
|
||||||
`var parse5Adapter = require('angular2/src/platform/server/parse5_adapter');\r\n` +
|
|
||||||
`parse5Adapter.Parse5DomAdapter.makeCurrent();`
|
|
||||||
},
|
|
||||||
{match: /$/, replacement: (_: any, relativePath: string) => '\r\n main(); \r\n'}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prepend 'use strict' directive to all JS files.
|
|
||||||
// See https://github.com/Microsoft/TypeScript/issues/3576
|
|
||||||
nodeTree = replace(
|
|
||||||
nodeTree, {files: ['**/*.js'], patterns: [{match: /^/, replacement: () => `'use strict';`}]});
|
|
||||||
|
|
||||||
return destCopy(nodeTree, destinationPath);
|
|
||||||
};
|
|
||||||
|
|
||||||
function compileTree(
|
|
||||||
tree: BroccoliTree, genInternalTypings: boolean, rootFilePaths: string[] = []) {
|
|
||||||
return compileWithTypescript(tree, {
|
|
||||||
// build pipeline options
|
|
||||||
'rootFilePaths': rootFilePaths,
|
|
||||||
'internalTypings': genInternalTypings,
|
|
||||||
// tsc options
|
|
||||||
'emitDecoratorMetadata': true,
|
|
||||||
'experimentalDecorators': true,
|
|
||||||
'declaration': true,
|
|
||||||
'stripInternal': true,
|
|
||||||
'module': 'commonjs',
|
|
||||||
'moduleResolution': 'classic',
|
|
||||||
'noEmitOnError': true,
|
|
||||||
'rootDir': '.',
|
|
||||||
'inlineSourceMap': true,
|
|
||||||
'inlineSources': true,
|
|
||||||
'target': 'es5'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractDocs(tree: BroccoliTree) {
|
|
||||||
var docs = new Funnel(tree, {include: ['**/*.md', '**/*.png'], exclude: ['**/*.dart.md']});
|
|
||||||
return stew.rename(docs, 'README.js.md', 'README.md');
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractPkgJsons(tree: BroccoliTree, BASE_PACKAGE_JSON: any) {
|
|
||||||
// Generate shared package.json info
|
|
||||||
var COMMON_PACKAGE_JSON = {
|
|
||||||
version: BASE_PACKAGE_JSON.version,
|
|
||||||
homepage: BASE_PACKAGE_JSON.homepage,
|
|
||||||
bugs: BASE_PACKAGE_JSON.bugs,
|
|
||||||
license: BASE_PACKAGE_JSON.license,
|
|
||||||
repository: BASE_PACKAGE_JSON.repository,
|
|
||||||
contributors: BASE_PACKAGE_JSON.contributors,
|
|
||||||
dependencies: BASE_PACKAGE_JSON.dependencies,
|
|
||||||
devDependencies: BASE_PACKAGE_JSON.devDependencies,
|
|
||||||
defaultDevDependencies: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
var packageJsons = new Funnel(tree, {include: ['**/package.json']});
|
|
||||||
return renderLodashTemplate(packageJsons, {context: {'packageJson': COMMON_PACKAGE_JSON}});
|
|
||||||
}
|
|
Loading…
Reference in New Issue