angular-cn/tools/broccoli/broccoli-merge-trees.ts

136 lines
4.5 KiB
TypeScript

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, destPath) {
let dirname = path.dirname(destPath);
fse.mkdirsSync(dirname, {fs: fs});
symlinkOrCopySync(sourcePath, destPath);
}
function pathOverwrittenError(path) {
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, val) => {
for (let i = 0, ii = cache.length; i < ii; ++i) {
if (cache[i] === val) return true;
}
return false;
};
let emit = (relativePath) => {
// ASSERT(!emitted[relativePath]);
pathsToEmit.push(relativePath);
emitted[relativePath] = true;
};
if (this.firstBuild) {
this.firstBuild = false;
// Build initial cache
treeDiffs.reverse().forEach((treeDiff: DiffResult, index) => {
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) => {
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);