diff --git a/lib/broccoli/broccoli-dest-copy.js b/lib/broccoli/broccoli-dest-copy.js
new file mode 100644
index 000000000000..c315be791ca0
--- /dev/null
+++ b/lib/broccoli/broccoli-dest-copy.js
@@ -0,0 +1,37 @@
+///
+///
+var fs = require('fs');
+var fse = require('fs-extra');
+var path = require('path');
+var diffing_broccoli_plugin_1 = require('./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.
+ */
+var DestCopy = (function () {
+ function DestCopy(inputPath, cachePath, outputRoot) {
+ this.inputPath = inputPath;
+ this.cachePath = cachePath;
+ this.outputRoot = outputRoot;
+ }
+ DestCopy.prototype.rebuild = function (treeDiff) {
+ var _this = this;
+ treeDiff.addedPaths.concat(treeDiff.changedPaths)
+ .forEach(function (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(function (removedFilePath) {
+ var destFilePath = path.join(_this.outputRoot, removedFilePath);
+ // TODO: what about obsolete directories? we are not cleaning those up yet
+ fs.unlinkSync(destFilePath);
+ });
+ };
+ return DestCopy;
+})();
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.default = diffing_broccoli_plugin_1.wrapDiffingPlugin(DestCopy);
+
+//# sourceMappingURL=broccoli-dest-copy.js.map
diff --git a/lib/broccoli/broccoli-dest-copy.js.map b/lib/broccoli/broccoli-dest-copy.js.map
new file mode 100644
index 000000000000..d7956090b571
--- /dev/null
+++ b/lib/broccoli/broccoli-dest-copy.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["broccoli/broccoli-dest-copy.ts"],"names":["DestCopy","DestCopy.constructor","DestCopy.rebuild"],"mappings":"AAAA,kDAAkD;AAClD,0DAA0D;AAE1D,IAAO,EAAE,WAAW,IAAI,CAAC,CAAC;AAC1B,IAAO,GAAG,WAAW,UAAU,CAAC,CAAC;AACjC,IAAO,IAAI,WAAW,MAAM,CAAC,CAAC;AAC9B,wCAAmE,2BAA2B,CAAC,CAAA;AAE/F;;;GAGG;AACH;IACEA,kBAAoBA,SAASA,EAAUA,SAASA,EAAUA,UAAkBA;QAAxDC,cAASA,GAATA,SAASA,CAAAA;QAAUA,cAASA,GAATA,SAASA,CAAAA;QAAUA,eAAUA,GAAVA,UAAUA,CAAQA;IAAGA,CAACA;IAGhFD,0BAAOA,GAAPA,UAAQA,QAAoBA;QAA5BE,iBAgBCA;QAfCA,QAAQA,CAACA,UAAUA,CAACA,MAAMA,CAACA,QAAQA,CAACA,YAAYA,CAACA;aAC5CA,OAAOA,CAACA,UAACA,eAAeA;YACvBA,IAAIA,YAAYA,GAAGA,IAAIA,CAACA,IAAIA,CAACA,KAAIA,CAACA,UAAUA,EAAEA,eAAeA,CAACA,CAACA;YAE/DA,IAAIA,WAAWA,GAAGA,IAAIA,CAACA,OAAOA,CAACA,YAAYA,CAACA,CAACA;YAC7CA,GAAGA,CAACA,UAAUA,CAACA,WAAWA,CAACA,CAACA;YAC5BA,GAAGA,CAACA,QAAQA,CAACA,IAAIA,CAACA,IAAIA,CAACA,KAAIA,CAACA,SAASA,EAAEA,eAAeA,CAACA,EAAEA,YAAYA,CAACA,CAACA;QACzEA,CAACA,CAACA,CAACA;QAEPA,QAAQA,CAACA,YAAYA,CAACA,OAAOA,CAACA,UAACA,eAAeA;YAC5CA,IAAIA,YAAYA,GAAGA,IAAIA,CAACA,IAAIA,CAACA,KAAIA,CAACA,UAAUA,EAAEA,eAAeA,CAACA,CAACA;YAE/DA,0EAA0EA;YAC1EA,EAAEA,CAACA,UAAUA,CAACA,YAAYA,CAACA,CAACA;QAC9BA,CAACA,CAACA,CAACA;IACLA,CAACA;IACHF,eAACA;AAADA,CArBA,AAqBCA,IAAA;AAED;kBAAe,2CAAiB,CAAC,QAAQ,CAAC,CAAC","file":"broccoli/broccoli-dest-copy.js","sourcesContent":["/// \n/// \n\nimport fs = require('fs');\nimport fse = require('fs-extra');\nimport path = require('path');\nimport {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';\n\n/**\n * Intercepts each file as it is copied to the destination tempdir,\n * and tees a copy to the given path outside the tmp dir.\n */\nclass DestCopy implements DiffingBroccoliPlugin {\n constructor(private inputPath, private cachePath, private outputRoot: string) {}\n\n\n rebuild(treeDiff: DiffResult) {\n treeDiff.addedPaths.concat(treeDiff.changedPaths)\n .forEach((changedFilePath) => {\n var destFilePath = path.join(this.outputRoot, changedFilePath);\n\n var destDirPath = path.dirname(destFilePath);\n fse.mkdirsSync(destDirPath);\n fse.copySync(path.join(this.inputPath, changedFilePath), destFilePath);\n });\n\n treeDiff.removedPaths.forEach((removedFilePath) => {\n var destFilePath = path.join(this.outputRoot, removedFilePath);\n\n // TODO: what about obsolete directories? we are not cleaning those up yet\n fs.unlinkSync(destFilePath);\n });\n }\n}\n\nexport default wrapDiffingPlugin(DestCopy);\n"],"sourceRoot":"/source/"}
\ No newline at end of file
diff --git a/lib/broccoli/broccoli-flatten.js b/lib/broccoli/broccoli-flatten.js
new file mode 100644
index 000000000000..b722e622954e
--- /dev/null
+++ b/lib/broccoli/broccoli-flatten.js
@@ -0,0 +1,51 @@
+var fs = require('fs');
+var fse = require('fs-extra');
+var path = require('path');
+var diffing_broccoli_plugin_1 = require('./diffing-broccoli-plugin');
+var symlinkOrCopy = require('symlink-or-copy').sync;
+var isWindows = process.platform === 'win32';
+/**
+ * Intercepts each changed file and replaces its contents with
+ * the associated changes.
+ */
+var DiffingFlatten = (function () {
+ function DiffingFlatten(inputPath, cachePath, options) {
+ this.inputPath = inputPath;
+ this.cachePath = cachePath;
+ this.options = options;
+ }
+ DiffingFlatten.prototype.rebuild = function (treeDiff) {
+ var _this = this;
+ var 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(function (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(function (removedFilePath) {
+ var destFilePath = path.join(_this.cachePath, path.basename(removedFilePath));
+ fs.unlinkSync(destFilePath);
+ });
+ };
+ return DiffingFlatten;
+})();
+exports.DiffingFlatten = DiffingFlatten;
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.default = diffing_broccoli_plugin_1.wrapDiffingPlugin(DiffingFlatten);
+
+//# sourceMappingURL=broccoli-flatten.js.map
diff --git a/lib/broccoli/broccoli-flatten.js.map b/lib/broccoli/broccoli-flatten.js.map
new file mode 100644
index 000000000000..acc06d9e44c9
--- /dev/null
+++ b/lib/broccoli/broccoli-flatten.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["broccoli/broccoli-flatten.ts"],"names":["DiffingFlatten","DiffingFlatten.constructor","DiffingFlatten.rebuild"],"mappings":"AAAA,IAAO,EAAE,WAAW,IAAI,CAAC,CAAC;AAC1B,IAAO,GAAG,WAAW,UAAU,CAAC,CAAC;AACjC,IAAO,IAAI,WAAW,MAAM,CAAC,CAAC;AAC9B,wCAAmE,2BAA2B,CAAC,CAAA;AAC/F,IAAI,aAAa,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC;AAEpD,IAAI,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AAG7C;;;GAGG;AACH;IACEA,wBAAoBA,SAASA,EAAUA,SAASA,EAAUA,OAAOA;QAA7CC,cAASA,GAATA,SAASA,CAAAA;QAAUA,cAASA,GAATA,SAASA,CAAAA;QAAUA,YAAOA,GAAPA,OAAOA,CAAAA;IAAGA,CAACA;IAGrED,gCAAOA,GAAPA,UAAQA,QAAoBA;QAA5BE,iBA8BCA;QA7BCA,IAAIA,aAAaA,GAAGA,QAAQA,CAACA,UAAUA,CAACA;QAExCA,qFAAqFA;QACrFA,sEAAsEA;QACtEA,EAAEA,CAACA,CAACA,SAASA,CAACA,CAACA,CAACA;YACdA,aAAaA,GAAGA,aAAaA,CAACA,MAAMA,CAACA,QAAQA,CAACA,YAAYA,CAACA,CAACA;QAC9DA,CAACA;QAEDA,aAAaA,CAACA,OAAOA,CAACA,UAACA,eAAeA;YACpCA,IAAIA,cAAcA,GAAGA,IAAIA,CAACA,IAAIA,CAACA,KAAIA,CAACA,SAASA,EAAEA,eAAeA,CAACA,CAACA;YAChEA,IAAIA,YAAYA,GAAGA,IAAIA,CAACA,IAAIA,CAACA,KAAIA,CAACA,SAASA,EAAEA,IAAIA,CAACA,QAAQA,CAACA,eAAeA,CAACA,CAACA,CAACA;YAC7EA,IAAIA,WAAWA,GAAGA,IAAIA,CAACA,OAAOA,CAACA,YAAYA,CAACA,CAACA;YAE7CA,EAAEA,CAACA,CAACA,CAACA,EAAEA,CAACA,UAAUA,CAACA,WAAWA,CAACA,CAACA,CAACA,CAACA;gBAChCA,GAAGA,CAACA,UAAUA,CAACA,WAAWA,CAACA,CAACA;YAC9BA,CAACA;YAEDA,EAAEA,CAACA,CAACA,CAACA,EAAEA,CAACA,UAAUA,CAACA,YAAYA,CAACA,CAACA,CAACA,CAACA;gBACjCA,aAAaA,CAACA,cAAcA,EAAEA,YAAYA,CAACA,CAACA;YAC9CA,CAACA;YAACA,IAAIA,CAACA,CAACA;gBACNA,MAAMA,IAAIA,KAAKA,CAACA,sBAAmBA,IAAIA,CAACA,QAAQA,CAACA,eAAeA,CAACA,QAAIA;oBACrDA,qBAAkBA,eAAeA,OAAGA,CAACA,CAACA;YACxDA,CAACA;QACHA,CAACA,CAACA,CAACA;QAEHA,QAAQA,CAACA,YAAYA,CAACA,OAAOA,CAACA,UAACA,eAAeA;YAC5CA,IAAIA,YAAYA,GAAGA,IAAIA,CAACA,IAAIA,CAACA,KAAIA,CAACA,SAASA,EAAEA,IAAIA,CAACA,QAAQA,CAACA,eAAeA,CAACA,CAACA,CAACA;YAC7EA,EAAEA,CAACA,UAAUA,CAACA,YAAYA,CAACA,CAACA;QAC9BA,CAACA,CAACA,CAACA;IACLA,CAACA;IACHF,qBAACA;AAADA,CAnCA,AAmCCA,IAAA;AAnCY,sBAAc,iBAmC1B,CAAA;AAED;kBAAe,2CAAiB,CAAC,cAAc,CAAC,CAAC","file":"broccoli/broccoli-flatten.js","sourcesContent":["import fs = require('fs');\nimport fse = require('fs-extra');\nimport path = require('path');\nimport {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';\nvar symlinkOrCopy = require('symlink-or-copy').sync;\n\nvar isWindows = process.platform === 'win32';\n\n\n/**\n * Intercepts each changed file and replaces its contents with\n * the associated changes.\n */\nexport class DiffingFlatten implements DiffingBroccoliPlugin {\n constructor(private inputPath, private cachePath, private options) {}\n\n\n rebuild(treeDiff: DiffResult) {\n let pathsToUpdate = treeDiff.addedPaths;\n\n // since we need to run on Windows as well we can't rely on symlinks being available,\n // which means that we need to respond to both added and changed paths\n if (isWindows) {\n pathsToUpdate = pathsToUpdate.concat(treeDiff.changedPaths);\n }\n\n pathsToUpdate.forEach((changedFilePath) => {\n var sourceFilePath = path.join(this.inputPath, changedFilePath);\n var destFilePath = path.join(this.cachePath, path.basename(changedFilePath));\n var destDirPath = path.dirname(destFilePath);\n\n if (!fs.existsSync(destDirPath)) {\n fse.mkdirpSync(destDirPath);\n }\n\n if (!fs.existsSync(destFilePath)) {\n symlinkOrCopy(sourceFilePath, destFilePath);\n } else {\n throw new Error(`Duplicate file '${path.basename(changedFilePath)}' ` +\n `found in path '${changedFilePath}'`);\n }\n });\n\n treeDiff.removedPaths.forEach((removedFilePath) => {\n var destFilePath = path.join(this.cachePath, path.basename(removedFilePath));\n fs.unlinkSync(destFilePath);\n });\n }\n}\n\nexport default wrapDiffingPlugin(DiffingFlatten);\n"],"sourceRoot":"/source/"}
\ No newline at end of file
diff --git a/lib/broccoli/broccoli-merge-trees.js b/lib/broccoli/broccoli-merge-trees.js
new file mode 100644
index 000000000000..4e4470a50297
--- /dev/null
+++ b/lib/broccoli/broccoli-merge-trees.js
@@ -0,0 +1,131 @@
+var fs = require('fs');
+var fse = require('fs-extra');
+var path = require('path');
+var symlinkOrCopySync = require('symlink-or-copy').sync;
+var diffing_broccoli_plugin_1 = require('./diffing-broccoli-plugin');
+var isWindows = process.platform === 'win32';
+function outputFileSync(sourcePath, destPath) {
+ var dirname = path.dirname(destPath);
+ fse.mkdirsSync(dirname, { fs: fs });
+ symlinkOrCopySync(sourcePath, destPath);
+}
+function pathOverwrittenError(path) {
+ var 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);
+}
+var MergeTrees = (function () {
+ function MergeTrees(inputPaths, cachePath, options) {
+ if (options === void 0) { options = {}; }
+ this.inputPaths = inputPaths;
+ this.cachePath = cachePath;
+ this.pathCache = Object.create(null);
+ this.firstBuild = true;
+ this.options = options || {};
+ }
+ MergeTrees.prototype.rebuild = function (treeDiffs) {
+ var _this = this;
+ var overwrite = this.options.overwrite;
+ var pathsToEmit = [];
+ var pathsToRemove = [];
+ var emitted = Object.create(null);
+ var contains = function (cache, val) {
+ for (var i = 0, ii = cache.length; i < ii; ++i) {
+ if (cache[i] === val)
+ return true;
+ }
+ return false;
+ };
+ var emit = function (relativePath) {
+ // ASSERT(!emitted[relativePath]);
+ pathsToEmit.push(relativePath);
+ emitted[relativePath] = true;
+ };
+ if (this.firstBuild) {
+ this.firstBuild = false;
+ // Build initial cache
+ treeDiffs.reverse().forEach(function (treeDiff, index) {
+ index = treeDiffs.length - 1 - index;
+ treeDiff.addedPaths.forEach(function (changedPath) {
+ var 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(function (treeDiff, index) {
+ index = treeDiffs.length - 1 - index;
+ if (treeDiff.removedPaths) {
+ treeDiff.removedPaths.forEach(function (removedPath) {
+ var 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);
+ }
+ }
+ });
+ }
+ var pathsToUpdate = treeDiff.addedPaths;
+ if (isWindows) {
+ pathsToUpdate = pathsToUpdate.concat(treeDiff.changedPaths);
+ }
+ pathsToUpdate.forEach(function (changedPath) {
+ var 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(function (a, b) { return a - b; });
+ if (cache.length > 1 && !overwrite) {
+ throw pathOverwrittenError(changedPath);
+ }
+ if (cache[cache.length - 1] === index && !emitted[changedPath]) {
+ emit(changedPath);
+ }
+ }
+ });
+ });
+ }
+ pathsToRemove.forEach(function (destPath) { return fse.removeSync(destPath); });
+ pathsToEmit.forEach(function (emittedPath) {
+ var cache = _this.pathCache[emittedPath];
+ var destPath = path.join(_this.cachePath, emittedPath);
+ var sourceIndex = cache[cache.length - 1];
+ var sourceInputPath = _this.inputPaths[sourceIndex];
+ var sourcePath = path.join(sourceInputPath, emittedPath);
+ if (cache.length > 1) {
+ fse.removeSync(destPath);
+ }
+ outputFileSync(sourcePath, destPath);
+ });
+ };
+ return MergeTrees;
+})();
+exports.MergeTrees = MergeTrees;
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.default = diffing_broccoli_plugin_1.wrapDiffingPlugin(MergeTrees);
+
+//# sourceMappingURL=broccoli-merge-trees.js.map
diff --git a/lib/broccoli/broccoli-merge-trees.js.map b/lib/broccoli/broccoli-merge-trees.js.map
new file mode 100644
index 000000000000..ba24235797bf
--- /dev/null
+++ b/lib/broccoli/broccoli-merge-trees.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["broccoli/broccoli-merge-trees.ts"],"names":["outputFileSync","pathOverwrittenError","MergeTrees","MergeTrees.constructor","MergeTrees.rebuild"],"mappings":"AAAA,IAAO,EAAE,WAAW,IAAI,CAAC,CAAC;AAC1B,IAAO,GAAG,WAAW,UAAU,CAAC,CAAC;AACjC,IAAO,IAAI,WAAW,MAAM,CAAC,CAAC;AAC9B,IAAI,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC;AACxD,wCAAmE,2BAA2B,CAAC,CAAA;AAE/F,IAAI,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AAM7C,wBAAwB,UAAU,EAAE,QAAQ;IAC1CA,IAAIA,OAAOA,GAAGA,IAAIA,CAACA,OAAOA,CAACA,QAAQA,CAACA,CAACA;IACrCA,GAAGA,CAACA,UAAUA,CAACA,OAAOA,EAAEA,EAACA,EAAEA,EAAEA,EAAEA,EAACA,CAACA,CAACA;IAClCA,iBAAiBA,CAACA,UAAUA,EAAEA,QAAQA,CAACA,CAACA;AAC1CA,CAACA;AAED,8BAA8B,IAAI;IAChCC,IAAMA,GAAGA,GAAGA,8EAA8EA,CAACA;IAC3FA,MAAMA,CAACA,IAAIA,KAAKA,CAACA,uDAAoDA,IAAIA,aAAOA,GAAKA,CAACA,CAACA;AACzFA,CAACA;AAED;IAKEC,oBAAmBA,UAAoBA,EAASA,SAAiBA,EACrDA,OAA+BA;QAA/BC,uBAA+BA,GAA/BA,YAA+BA;QADxBA,eAAUA,GAAVA,UAAUA,CAAUA;QAASA,cAASA,GAATA,SAASA,CAAQA;QAJzDA,cAASA,GAA8BA,MAAMA,CAACA,MAAMA,CAACA,IAAIA,CAACA,CAACA;QAE3DA,eAAUA,GAAYA,IAAIA,CAACA;QAIjCA,IAAIA,CAACA,OAAOA,GAAGA,OAAOA,IAAIA,EAAEA,CAACA;IAC/BA,CAACA;IAEDD,4BAAOA,GAAPA,UAAQA,SAAuBA;QAA/BE,iBAoGCA;QAnGCA,IAAIA,SAASA,GAAGA,IAAIA,CAACA,OAAOA,CAACA,SAASA,CAACA;QACvCA,IAAIA,WAAWA,GAAaA,EAAEA,CAACA;QAC/BA,IAAIA,aAAaA,GAAaA,EAAEA,CAACA;QACjCA,IAAIA,OAAOA,GAA6BA,MAAMA,CAACA,MAAMA,CAACA,IAAIA,CAACA,CAACA;QAC5DA,IAAIA,QAAQA,GAAGA,UAACA,KAAKA,EAAEA,GAAGA;YACxBA,GAAGA,CAACA,CAACA,GAAGA,CAACA,CAACA,GAAGA,CAACA,EAAEA,EAAEA,GAAGA,KAAKA,CAACA,MAAMA,EAAEA,CAACA,GAAGA,EAAEA,EAAEA,EAAEA,CAACA,EAAEA,CAACA;gBAC/CA,EAAEA,CAACA,CAACA,KAAKA,CAACA,CAACA,CAACA,KAAKA,GAAGA,CAACA;oBAACA,MAAMA,CAACA,IAAIA,CAACA;YACpCA,CAACA;YACDA,MAAMA,CAACA,KAAKA,CAACA;QACfA,CAACA,CAACA;QAEFA,IAAIA,IAAIA,GAAGA,UAACA,YAAYA;YACtBA,kCAAkCA;YAClCA,WAAWA,CAACA,IAAIA,CAACA,YAAYA,CAACA,CAACA;YAC/BA,OAAOA,CAACA,YAAYA,CAACA,GAAGA,IAAIA,CAACA;QAC/BA,CAACA,CAACA;QAEFA,EAAEA,CAACA,CAACA,IAAIA,CAACA,UAAUA,CAACA,CAACA,CAACA;YACpBA,IAAIA,CAACA,UAAUA,GAAGA,KAAKA,CAACA;YAExBA,sBAAsBA;YACtBA,SAASA,CAACA,OAAOA,EAAEA,CAACA,OAAOA,CAACA,UAACA,QAAoBA,EAAEA,KAAKA;gBACtDA,KAAKA,GAAGA,SAASA,CAACA,MAAMA,GAAGA,CAACA,GAAGA,KAAKA,CAACA;gBACrCA,QAAQA,CAACA,UAAUA,CAACA,OAAOA,CAACA,UAACA,WAAWA;oBACtCA,IAAIA,KAAKA,GAAGA,KAAIA,CAACA,SAASA,CAACA,WAAWA,CAACA,CAACA;oBACxCA,EAAEA,CAACA,CAACA,KAAKA,KAAKA,SAASA,CAACA,CAACA,CAACA;wBACxBA,KAAIA,CAACA,SAASA,CAACA,WAAWA,CAACA,GAAGA,CAACA,KAAKA,CAACA,CAACA;wBACtCA,WAAWA,CAACA,IAAIA,CAACA,WAAWA,CAACA,CAACA;oBAChCA,CAACA;oBAACA,IAAIA,CAACA,EAAEA,CAACA,CAACA,SAASA,CAACA,CAACA,CAACA;wBACrBA,8CAA8CA;wBAC9CA,KAAKA,CAACA,OAAOA,CAACA,KAAKA,CAACA,CAACA;oBACvBA,CAACA;oBAACA,IAAIA,CAACA,CAACA;wBACNA,MAAMA,oBAAoBA,CAACA,WAAWA,CAACA,CAACA;oBAC1CA,CAACA;gBACHA,CAACA,CAACA,CAACA;YACLA,CAACA,CAACA,CAACA;QAELA,CAACA;QAACA,IAAIA,CAACA,CAACA;YACNA,eAAeA;YACfA,SAASA,CAACA,OAAOA,EAAEA,CAACA,OAAOA,CAACA,UAACA,QAAoBA,EAAEA,KAAKA;gBACtDA,KAAKA,GAAGA,SAASA,CAACA,MAAMA,GAAGA,CAACA,GAAGA,KAAKA,CAACA;gBACrCA,EAAEA,CAACA,CAACA,QAAQA,CAACA,YAAYA,CAACA,CAACA,CAACA;oBAC1BA,QAAQA,CAACA,YAAYA,CAACA,OAAOA,CAACA,UAACA,WAAWA;wBACxCA,IAAIA,KAAKA,GAAGA,KAAIA,CAACA,SAASA,CAACA,WAAWA,CAACA,CAACA;wBACxCA,+BAA+BA;wBAC/BA,kCAAkCA;wBAClCA,EAAEA,CAACA,CAACA,KAAKA,CAACA,KAAKA,CAACA,MAAMA,GAAGA,CAACA,CAACA,KAAKA,KAAKA,CAACA,CAACA,CAACA;4BACtCA,aAAaA,CAACA,IAAIA,CAACA,IAAIA,CAACA,IAAIA,CAACA,KAAIA,CAACA,SAASA,EAAEA,WAAWA,CAACA,CAACA,CAACA;4BAC3DA,KAAKA,CAACA,GAAGA,EAAEA,CAACA;4BACZA,EAAEA,CAACA,CAACA,KAAKA,CAACA,MAAMA,KAAKA,CAACA,CAACA,CAACA,CAACA;gCACvBA,KAAIA,CAACA,SAASA,CAACA,WAAWA,CAACA,GAAGA,SAASA,CAACA;4BAC1CA,CAACA;4BAACA,IAAIA,CAACA,EAAEA,CAACA,CAACA,CAACA,OAAOA,CAACA,WAAWA,CAACA,CAACA,CAACA,CAACA;gCACjCA,EAAEA,CAACA,CAACA,KAAKA,CAACA,MAAMA,KAAKA,CAACA,IAAIA,CAACA,SAASA,CAACA,CAACA,CAACA;oCACrCA,MAAMA,oBAAoBA,CAACA,WAAWA,CAACA,CAACA;gCAC1CA,CAACA;gCACDA,IAAIA,CAACA,WAAWA,CAACA,CAACA;4BACpBA,CAACA;wBACHA,CAACA;oBACHA,CAACA,CAACA,CAACA;gBACLA,CAACA;gBAEDA,IAAIA,aAAaA,GAAGA,QAAQA,CAACA,UAAUA,CAACA;gBAExCA,EAAEA,CAACA,CAACA,SAASA,CAACA,CAACA,CAACA;oBACdA,aAAaA,GAAGA,aAAaA,CAACA,MAAMA,CAACA,QAAQA,CAACA,YAAYA,CAACA,CAACA;gBAC9DA,CAACA;gBAEDA,aAAaA,CAACA,OAAOA,CAACA,UAACA,WAAWA;oBAChCA,IAAIA,KAAKA,GAAGA,KAAIA,CAACA,SAASA,CAACA,WAAWA,CAACA,CAACA;oBACxCA,EAAEA,CAACA,CAACA,KAAKA,KAAKA,SAASA,CAACA,CAACA,CAACA;wBACxBA,iBAAiBA;wBACjBA,KAAIA,CAACA,SAASA,CAACA,WAAWA,CAACA,GAAGA,CAACA,KAAKA,CAACA,CAACA;wBACtCA,IAAIA,CAACA,WAAWA,CAACA,CAACA;oBACpBA,CAACA;oBAACA,IAAIA,CAACA,EAAEA,CAACA,CAACA,CAACA,QAAQA,CAACA,KAAKA,EAAEA,KAAKA,CAACA,CAACA,CAACA,CAACA;wBACnCA,KAAKA,CAACA,IAAIA,CAACA,KAAKA,CAACA,CAACA;wBAClBA,KAAKA,CAACA,IAAIA,CAACA,UAACA,CAACA,EAAEA,CAACA,IAAKA,OAAAA,CAACA,GAAGA,CAACA,EAALA,CAAKA,CAACA,CAACA;wBAC5BA,EAAEA,CAACA,CAACA,KAAKA,CAACA,MAAMA,GAAGA,CAACA,IAAIA,CAACA,SAASA,CAACA,CAACA,CAACA;4BACnCA,MAAMA,oBAAoBA,CAACA,WAAWA,CAACA,CAACA;wBAC1CA,CAACA;wBACDA,EAAEA,CAACA,CAACA,KAAKA,CAACA,KAAKA,CAACA,MAAMA,GAAGA,CAACA,CAACA,KAAKA,KAAKA,IAAIA,CAACA,OAAOA,CAACA,WAAWA,CAACA,CAACA,CAACA,CAACA;4BAC/DA,IAAIA,CAACA,WAAWA,CAACA,CAACA;wBACpBA,CAACA;oBACHA,CAACA;gBACHA,CAACA,CAACA,CAACA;YACLA,CAACA,CAACA,CAACA;QACLA,CAACA;QAEDA,aAAaA,CAACA,OAAOA,CAACA,UAACA,QAAQA,IAAKA,OAAAA,GAAGA,CAACA,UAAUA,CAACA,QAAQA,CAACA,EAAxBA,CAAwBA,CAACA,CAACA;QAC9DA,WAAWA,CAACA,OAAOA,CAACA,UAACA,WAAWA;YAC9BA,IAAIA,KAAKA,GAAGA,KAAIA,CAACA,SAASA,CAACA,WAAWA,CAACA,CAACA;YACxCA,IAAIA,QAAQA,GAAGA,IAAIA,CAACA,IAAIA,CAACA,KAAIA,CAACA,SAASA,EAAEA,WAAWA,CAACA,CAACA;YACtDA,IAAIA,WAAWA,GAAGA,KAAKA,CAACA,KAAKA,CAACA,MAAMA,GAAGA,CAACA,CAACA,CAACA;YAC1CA,IAAIA,eAAeA,GAAGA,KAAIA,CAACA,UAAUA,CAACA,WAAWA,CAACA,CAACA;YACnDA,IAAIA,UAAUA,GAAGA,IAAIA,CAACA,IAAIA,CAACA,eAAeA,EAAEA,WAAWA,CAACA,CAACA;YACzDA,EAAEA,CAACA,CAACA,KAAKA,CAACA,MAAMA,GAAGA,CAACA,CAACA,CAACA,CAACA;gBACrBA,GAAGA,CAACA,UAAUA,CAACA,QAAQA,CAACA,CAACA;YAC3BA,CAACA;YACDA,cAAcA,CAACA,UAAUA,EAAEA,QAAQA,CAACA,CAACA;QACvCA,CAACA,CAACA,CAACA;IACLA,CAACA;IACHF,iBAACA;AAADA,CA/GA,AA+GCA,IAAA;AA/GY,kBAAU,aA+GtB,CAAA;AAED;kBAAe,2CAAiB,CAAC,UAAU,CAAC,CAAC","file":"broccoli/broccoli-merge-trees.js","sourcesContent":["import fs = require('fs');\nimport fse = require('fs-extra');\nimport path = require('path');\nvar symlinkOrCopySync = require('symlink-or-copy').sync;\nimport {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';\n\nvar isWindows = process.platform === 'win32';\n\ninterface MergeTreesOptions {\n overwrite?: boolean;\n}\n\nfunction outputFileSync(sourcePath, destPath) {\n let dirname = path.dirname(destPath);\n fse.mkdirsSync(dirname, {fs: fs});\n symlinkOrCopySync(sourcePath, destPath);\n}\n\nfunction pathOverwrittenError(path) {\n const msg = 'Either remove the duplicate or enable the \"overwrite\" option for this merge.';\n return new Error(`Duplicate path found while merging trees. Path: \"${path}\".\\n${msg}`);\n}\n\nexport class MergeTrees implements DiffingBroccoliPlugin {\n private pathCache: {[key: string]: number[]} = Object.create(null);\n public options: MergeTreesOptions;\n private firstBuild: boolean = true;\n\n constructor(public inputPaths: string[], public cachePath: string,\n options: MergeTreesOptions = {}) {\n this.options = options || {};\n }\n\n rebuild(treeDiffs: DiffResult[]) {\n let overwrite = this.options.overwrite;\n let pathsToEmit: string[] = [];\n let pathsToRemove: string[] = [];\n let emitted: {[key: string]: boolean} = Object.create(null);\n let contains = (cache, val) => {\n for (let i = 0, ii = cache.length; i < ii; ++i) {\n if (cache[i] === val) return true;\n }\n return false;\n };\n\n let emit = (relativePath) => {\n // ASSERT(!emitted[relativePath]);\n pathsToEmit.push(relativePath);\n emitted[relativePath] = true;\n };\n\n if (this.firstBuild) {\n this.firstBuild = false;\n\n // Build initial cache\n treeDiffs.reverse().forEach((treeDiff: DiffResult, index) => {\n index = treeDiffs.length - 1 - index;\n treeDiff.addedPaths.forEach((changedPath) => {\n let cache = this.pathCache[changedPath];\n if (cache === undefined) {\n this.pathCache[changedPath] = [index];\n pathsToEmit.push(changedPath);\n } else if (overwrite) {\n // ASSERT(contains(pathsToEmit, changedPath));\n cache.unshift(index);\n } else {\n throw pathOverwrittenError(changedPath);\n }\n });\n });\n\n } else {\n // Update cache\n treeDiffs.reverse().forEach((treeDiff: DiffResult, index) => {\n index = treeDiffs.length - 1 - index;\n if (treeDiff.removedPaths) {\n treeDiff.removedPaths.forEach((removedPath) => {\n let cache = this.pathCache[removedPath];\n // ASSERT(cache !== undefined);\n // ASSERT(contains(cache, index));\n if (cache[cache.length - 1] === index) {\n pathsToRemove.push(path.join(this.cachePath, removedPath));\n cache.pop();\n if (cache.length === 0) {\n this.pathCache[removedPath] = undefined;\n } else if (!emitted[removedPath]) {\n if (cache.length === 1 && !overwrite) {\n throw pathOverwrittenError(removedPath);\n }\n emit(removedPath);\n }\n }\n });\n }\n\n let pathsToUpdate = treeDiff.addedPaths;\n\n if (isWindows) {\n pathsToUpdate = pathsToUpdate.concat(treeDiff.changedPaths);\n }\n\n pathsToUpdate.forEach((changedPath) => {\n let cache = this.pathCache[changedPath];\n if (cache === undefined) {\n // File was added\n this.pathCache[changedPath] = [index];\n emit(changedPath);\n } else if (!contains(cache, index)) {\n cache.push(index);\n cache.sort((a, b) => a - b);\n if (cache.length > 1 && !overwrite) {\n throw pathOverwrittenError(changedPath);\n }\n if (cache[cache.length - 1] === index && !emitted[changedPath]) {\n emit(changedPath);\n }\n }\n });\n });\n }\n\n pathsToRemove.forEach((destPath) => fse.removeSync(destPath));\n pathsToEmit.forEach((emittedPath) => {\n let cache = this.pathCache[emittedPath];\n let destPath = path.join(this.cachePath, emittedPath);\n let sourceIndex = cache[cache.length - 1];\n let sourceInputPath = this.inputPaths[sourceIndex];\n let sourcePath = path.join(sourceInputPath, emittedPath);\n if (cache.length > 1) {\n fse.removeSync(destPath);\n }\n outputFileSync(sourcePath, destPath);\n });\n }\n}\n\nexport default wrapDiffingPlugin(MergeTrees);\n"],"sourceRoot":"/source/"}
\ No newline at end of file
diff --git a/lib/broccoli/broccoli-replace.js b/lib/broccoli/broccoli-replace.js
new file mode 100644
index 000000000000..10b529cc504e
--- /dev/null
+++ b/lib/broccoli/broccoli-replace.js
@@ -0,0 +1,62 @@
+var fs = require('fs');
+var fse = require('fs-extra');
+var path = require('path');
+var diffing_broccoli_plugin_1 = require('./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.
+ */
+var DiffingReplace = (function () {
+ function DiffingReplace(inputPath, cachePath, options) {
+ this.inputPath = inputPath;
+ this.cachePath = cachePath;
+ this.options = options;
+ }
+ DiffingReplace.prototype.rebuild = function (treeDiff) {
+ var _this = this;
+ var patterns = this.options.patterns;
+ var files = this.options.files;
+ treeDiff.addedPaths.concat(treeDiff.changedPaths)
+ .forEach(function (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(function (filePath) { return minimatch(changedFilePath, filePath); });
+ if (fileMatches) {
+ var content = fs.readFileSync(sourceFilePath, FILE_ENCODING);
+ patterns.forEach(function (pattern) {
+ var replacement = pattern.replacement;
+ if (typeof replacement === 'function') {
+ replacement = function (content) {
+ 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(function (removedFilePath) {
+ var destFilePath = path.join(_this.cachePath, removedFilePath);
+ fs.unlinkSync(destFilePath);
+ });
+ };
+ return DiffingReplace;
+})();
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.default = diffing_broccoli_plugin_1.wrapDiffingPlugin(DiffingReplace);
+
+//# sourceMappingURL=broccoli-replace.js.map
diff --git a/lib/broccoli/broccoli-replace.js.map b/lib/broccoli/broccoli-replace.js.map
new file mode 100644
index 000000000000..62c00cbf695c
--- /dev/null
+++ b/lib/broccoli/broccoli-replace.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["broccoli/broccoli-replace.ts"],"names":["DiffingReplace","DiffingReplace.constructor","DiffingReplace.rebuild"],"mappings":"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","file":"broccoli/broccoli-replace.js","sourcesContent":["import fs = require('fs');\nimport fse = require('fs-extra');\nimport path = require('path');\nimport {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';\n\nvar minimatch = require('minimatch');\nvar FILE_ENCODING = {encoding: 'utf-8'};\n\n/**\n * Intercepts each changed file and replaces its contents with\n * the associated changes.\n */\nclass DiffingReplace implements DiffingBroccoliPlugin {\n constructor(private inputPath, private cachePath, private options) {}\n\n rebuild(treeDiff: DiffResult) {\n var patterns = this.options.patterns;\n var files = this.options.files;\n\n treeDiff.addedPaths.concat(treeDiff.changedPaths)\n .forEach((changedFilePath) => {\n var sourceFilePath = path.join(this.inputPath, changedFilePath);\n var destFilePath = path.join(this.cachePath, changedFilePath);\n var destDirPath = path.dirname(destFilePath);\n\n if (!fs.existsSync(destDirPath)) {\n fse.mkdirpSync(destDirPath);\n }\n\n var fileMatches = files.some((filePath) => minimatch(changedFilePath, filePath));\n if (fileMatches) {\n var content = fs.readFileSync(sourceFilePath, FILE_ENCODING);\n patterns.forEach((pattern) => {\n var replacement = pattern.replacement;\n if (typeof replacement === 'function') {\n replacement = function(content) {\n return pattern.replacement(content, changedFilePath);\n };\n }\n content = content.replace(pattern.match, replacement);\n });\n fs.writeFileSync(destFilePath, content, FILE_ENCODING);\n } else if (!fs.existsSync(destFilePath)) {\n try {\n fs.symlinkSync(sourceFilePath, destFilePath);\n } catch (e) {\n fs.writeFileSync(destFilePath, fs.readFileSync(sourceFilePath));\n }\n }\n });\n\n treeDiff.removedPaths.forEach((removedFilePath) => {\n var destFilePath = path.join(this.cachePath, removedFilePath);\n fs.unlinkSync(destFilePath);\n });\n }\n}\n\nexport default wrapDiffingPlugin(DiffingReplace);\n"],"sourceRoot":"/source/"}
\ No newline at end of file