chore(tools): remove unused files

This commit is contained in:
Victor Berchet 2016-07-21 11:49:22 -07:00
parent e73ac1e992
commit cdb3678fe3
33 changed files with 0 additions and 3718 deletions

View File

@ -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);
}
};

View File

@ -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);
}

View File

@ -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;

View File

@ -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[]) {}
}

View File

@ -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);

View File

@ -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');
}

View File

@ -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);

View File

@ -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;

View File

@ -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\'');
});
});

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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.');
});
});

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
}

View File

@ -1,6 +0,0 @@
declare module 'broccoli-writer' {
class Writer {
write(readTree: (tree: BroccoliTree) => Promise<string>, destDir: string): Promise<any>;
}
export = Writer;
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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'));
}

View File

@ -1,2 +0,0 @@
importScripts("es6-shim.js", "zone.js", "long-stack-trace-zone.js", "system.src.js",
"Reflect.js");

View File

@ -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'));
}

View File

@ -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);
});
});
});
}
}

View File

@ -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']);
});
});
});

View File

@ -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
}

View File

@ -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);
};

View File

@ -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);
};

View File

@ -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}});
}