2015-05-04 11:19:25 -04:00
|
|
|
/// <reference path="broccoli.d.ts" />
|
|
|
|
|
|
|
|
import fs = require('fs');
|
|
|
|
import fse = require('fs-extra');
|
|
|
|
import path = require('path');
|
|
|
|
import {TreeDiffer, DiffResult} from './tree-differ';
|
2015-05-24 20:27:38 -04:00
|
|
|
import stabilizeTree from './broccoli-tree-stabilizer';
|
2015-05-04 11:19:25 -04:00
|
|
|
let symlinkOrCopy = require('symlink-or-copy');
|
|
|
|
|
|
|
|
export {DiffResult} from './tree-differ';
|
|
|
|
|
2016-01-22 13:51:16 -05:00
|
|
|
export type PluginClass = any;
|
2015-05-04 11:19:25 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Makes writing diffing plugins easy.
|
|
|
|
*
|
|
|
|
* Factory method that takes a class that implements the DiffingBroccoliPlugin interface and returns
|
|
|
|
* an instance of BroccoliTree.
|
|
|
|
*/
|
2016-01-22 13:51:16 -05:00
|
|
|
export function wrapDiffingPlugin(pluginClass: PluginClass): DiffingPluginWrapperFactory {
|
|
|
|
return function() { return new DiffingPluginWrapper(pluginClass, arguments) };
|
2015-05-04 11:19:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DiffingBroccoliPlugin {
|
2016-04-12 12:40:37 -04:00
|
|
|
rebuild(diff: (DiffResult | DiffResult[])): (Promise<DiffResult | void>| DiffResult | void);
|
|
|
|
cleanup ? () : void;
|
2015-05-04 11:19:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-01-22 13:51:16 -05:00
|
|
|
export type DiffingPluginWrapperFactory =
|
|
|
|
(inputTrees: (BroccoliTree | BroccoliTree[]), options?: any) => BroccoliTree;
|
2015-05-04 11:19:25 -04:00
|
|
|
|
|
|
|
|
|
|
|
class DiffingPluginWrapper implements BroccoliTree {
|
2015-05-21 12:07:23 -04:00
|
|
|
treeDiffer: TreeDiffer = null;
|
|
|
|
treeDiffers: TreeDiffer[] = null;
|
2015-05-04 11:19:25 -04:00
|
|
|
initialized = false;
|
|
|
|
wrappedPlugin: DiffingBroccoliPlugin = null;
|
2016-01-22 13:51:16 -05:00
|
|
|
inputTree: BroccoliTree = null;
|
|
|
|
inputTrees: BroccoliTree[] = null;
|
|
|
|
description: string = null;
|
2015-05-04 11:19:25 -04:00
|
|
|
|
|
|
|
// props monkey-patched by broccoli builder:
|
2016-01-22 13:51:16 -05:00
|
|
|
inputPath: string = null;
|
|
|
|
inputPaths: string[] = null;
|
|
|
|
cachePath: string = null;
|
|
|
|
outputPath: string = null;
|
2015-05-04 11:19:25 -04:00
|
|
|
|
2015-06-25 19:59:35 -04:00
|
|
|
private diffResult: DiffResult = null;
|
|
|
|
|
2016-01-22 13:51:16 -05:00
|
|
|
constructor(private pluginClass: PluginClass, private wrappedPluginArguments: IArguments) {
|
2015-05-04 11:19:25 -04:00
|
|
|
if (Array.isArray(wrappedPluginArguments[0])) {
|
2015-05-24 20:27:38 -04:00
|
|
|
this.inputTrees = this.stabilizeTrees(wrappedPluginArguments[0]);
|
2015-05-04 11:19:25 -04:00
|
|
|
} else {
|
2015-05-24 20:27:38 -04:00
|
|
|
this.inputTree = this.stabilizeTree(wrappedPluginArguments[0]);
|
2015-05-04 11:19:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
this.description = this.pluginClass.name;
|
|
|
|
}
|
|
|
|
|
2016-04-12 12:40:37 -04:00
|
|
|
private getDiffResult(): (DiffResult | DiffResult[]) {
|
2016-01-22 13:51:16 -05:00
|
|
|
let returnOrCalculateDiffResult = (tree: BroccoliTree, index: number) => {
|
2015-06-25 19:59:35 -04:00
|
|
|
// 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.
|
2016-01-22 13:51:16 -05:00
|
|
|
let diffResult = this.diffResult;
|
2015-06-29 10:59:41 -04:00
|
|
|
if (diffResult) return diffResult;
|
2016-01-22 13:51:16 -05:00
|
|
|
let differ = index === -1 ? this.treeDiffer : this.treeDiffers[index];
|
2015-06-29 10:59:41 -04:00
|
|
|
return differ.diffTree();
|
2015-06-25 19:59:35 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
if (this.inputTrees) {
|
|
|
|
return this.inputTrees.map(returnOrCalculateDiffResult);
|
|
|
|
} else if (this.inputTree) {
|
2016-01-22 13:51:16 -05:00
|
|
|
return returnOrCalculateDiffResult(this.inputTree, -1);
|
2015-05-21 12:07:23 -04:00
|
|
|
} else {
|
2016-04-12 12:40:37 -04:00
|
|
|
throw new Error("Missing TreeDiffer");
|
2015-05-21 12:07:23 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-12 12:40:37 -04:00
|
|
|
private maybeStoreDiffResult(value: (DiffResult | void)) {
|
2015-06-26 07:35:41 -04:00
|
|
|
if (!(value instanceof DiffResult)) value = null;
|
|
|
|
this.diffResult = <DiffResult>(value);
|
2015-06-25 19:59:35 -04:00
|
|
|
}
|
2015-05-21 12:07:23 -04:00
|
|
|
|
2016-04-12 12:40:37 -04:00
|
|
|
rebuild(): (Promise<any>| void) {
|
2015-05-04 11:27:14 -04:00
|
|
|
try {
|
|
|
|
let firstRun = !this.initialized;
|
|
|
|
this.init();
|
2015-05-04 11:19:25 -04:00
|
|
|
|
2015-06-25 19:59:35 -04:00
|
|
|
let diffResult = this.getDiffResult();
|
2015-05-04 11:19:25 -04:00
|
|
|
|
2015-06-25 19:59:35 -04:00
|
|
|
let result = this.wrappedPlugin.rebuild(diffResult);
|
2015-05-04 11:19:25 -04:00
|
|
|
|
2015-06-25 19:59:35 -04:00
|
|
|
if (result) {
|
2016-04-12 12:40:37 -04:00
|
|
|
let resultPromise = <Promise<DiffResult | void>>(result);
|
2015-06-25 19:59:35 -04:00
|
|
|
if (resultPromise.then) {
|
|
|
|
// rebuild() -> Promise<>
|
|
|
|
return resultPromise.then((result: (DiffResult | void)) => {
|
|
|
|
this.maybeStoreDiffResult(result);
|
|
|
|
this.relinkOutputAndCachePaths();
|
|
|
|
});
|
|
|
|
}
|
2015-05-04 11:27:14 -04:00
|
|
|
}
|
2015-05-04 11:19:25 -04:00
|
|
|
|
2015-06-25 19:59:35 -04:00
|
|
|
this.maybeStoreDiffResult(<(DiffResult | void)>(result));
|
2015-05-04 11:27:14 -04:00
|
|
|
this.relinkOutputAndCachePaths();
|
|
|
|
} catch (e) {
|
|
|
|
e.message = `[${this.description}]: ${e.message}`;
|
|
|
|
throw e;
|
|
|
|
}
|
2015-05-04 11:19:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-05-24 20:27:38 -04:00
|
|
|
cleanup() {
|
2015-07-11 11:26:48 -04:00
|
|
|
if (this.wrappedPlugin && this.wrappedPlugin.cleanup) {
|
2015-05-24 20:27:38 -04:00
|
|
|
this.wrappedPlugin.cleanup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-05-04 11:19:25 -04:00
|
|
|
private relinkOutputAndCachePaths() {
|
|
|
|
// just symlink the cache and output tree
|
|
|
|
fs.rmdirSync(this.outputPath);
|
|
|
|
symlinkOrCopy.sync(this.cachePath, this.outputPath);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private init() {
|
|
|
|
if (!this.initialized) {
|
2015-05-06 19:24:10 -04:00
|
|
|
let includeExtensions = this.pluginClass.includeExtensions || [];
|
|
|
|
let excludeExtensions = this.pluginClass.excludeExtensions || [];
|
2015-05-21 12:07:23 -04:00
|
|
|
let description = this.description;
|
2015-05-04 11:19:25 -04:00
|
|
|
this.initialized = true;
|
2015-05-21 12:07:23 -04:00
|
|
|
if (this.inputPaths) {
|
2016-04-12 12:40:37 -04:00
|
|
|
this.treeDiffers =
|
|
|
|
this.inputPaths.map((inputPath) => new TreeDiffer(
|
|
|
|
description, inputPath, includeExtensions, excludeExtensions));
|
2015-05-21 12:07:23 -04:00
|
|
|
} else if (this.inputPath) {
|
|
|
|
this.treeDiffer =
|
|
|
|
new TreeDiffer(description, this.inputPath, includeExtensions, excludeExtensions);
|
|
|
|
}
|
2016-04-12 12:40:37 -04:00
|
|
|
this.wrappedPlugin = new this.pluginClass(this.inputPaths || this.inputPath, this.cachePath,
|
|
|
|
this.wrappedPluginArguments[1]);
|
2015-05-04 11:19:25 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-05-24 20:27:38 -04:00
|
|
|
private stabilizeTrees(trees: BroccoliTree[]) {
|
2015-06-05 03:19:34 -04:00
|
|
|
// Prevent extensions to prevent array from being mutated from the outside.
|
|
|
|
// For-loop used to avoid re-allocating a new array.
|
2016-01-22 13:51:16 -05:00
|
|
|
var stableTrees: BroccoliTree[] = [];
|
2015-06-05 03:19:34 -04:00
|
|
|
for (let i = 0; i < trees.length; ++i) {
|
2015-11-13 05:00:32 -05:00
|
|
|
// 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!');
|
2015-06-05 03:19:34 -04:00
|
|
|
}
|
2015-11-13 05:00:32 -05:00
|
|
|
|
|
|
|
return Object.freeze(stableTrees);
|
2015-05-24 20:27:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
2015-06-07 17:56:35 -04:00
|
|
|
//
|
2015-11-13 05:00:32 -05:00
|
|
|
// New-style/rebuild trees should always be stable.
|
2016-01-22 13:51:16 -05:00
|
|
|
let isNewStyleTree =
|
|
|
|
!!(tree['newStyleTree'] || typeof tree.rebuild === 'function' ||
|
|
|
|
(<any>tree)['isReadAPICompatTree'] || (<any>tree).constructor['name'] === 'Funnel');
|
2015-06-07 17:56:35 -04:00
|
|
|
|
|
|
|
return isNewStyleTree ? tree : stabilizeTree(tree);
|
2015-05-04 11:19:25 -04:00
|
|
|
}
|
|
|
|
}
|