builder.js #1

  • //
  • guest/
  • jen_bottom/
  • ember/
  • tutorial/
  • testapp/
  • node_modules/
  • broccoli-builder/
  • lib/
  • builder.js
  • View
  • Commits
  • Open Download .zip Download (9 KB)
var RSVP = require('rsvp')
var mapSeries = require('promise-map-series')
var apiCompat = require('./api_compat')
var heimdall = require('heimdalljs');
var SilentError = require('silent-error');
var BroccoliBuildError = require('./broccoli-build-error');

exports.Builder = Builder
function Builder (tree) {
  this.tree = tree
  this.allTreesRead = [] // across all builds
  this._currentStep = undefined;
  this.canceled = false;
  this.isThrown = false;
}

function wrapStringErrors(reason) {
  var err

  if (typeof reason === 'string') {
    err = new Error(reason + ' [string exception]')
  } else {
    err = reason
  }

  throw err
}

function summarize(node) {
  return {
    directory: node.directory,
    graph: node,
  }
}

RSVP.EventTarget.mixin(Builder.prototype)

Builder.prototype.build = function (willReadStringTree) {
  if (this.canceled) {
    return RSVP.Promise.reject(new Error('cannot build this builder, as it has been previously canceled'));
  }
  var builder = this

  var newTreesRead = []
  var nodeCache = []
  this.isThrown = false;

  this._currentStep = RSVP.Promise.resolve()
    .then(function () {
      // added for backwards compat. Can be safely removed for 1.0.0
      builder.trigger('start');
      return readAndReturnNodeFor(builder.tree) // call builder.tree.read()
    })
    .then(summarize)
    .finally(appendNewTreesRead)
    .finally(unsetCurrentStep)
    .finally(function() {
      // added for backwards compat. Can be safely removed for 1.0.0
      builder.trigger('end');
    })
    .catch(wrapStringErrors)

  return this._currentStep;

  function unsetCurrentStep() {
    builder._currentStep = null
  }


  function appendNewTreesRead() {
    for (var i = 0; i < newTreesRead.length; i++) {
      if (builder.allTreesRead.indexOf(newTreesRead[i]) === -1) {
        builder.allTreesRead.push(newTreesRead[i])
      }
    }
  }

  // Read the `tree` and return its node, which in particular contains the
  // tree's output directory (node.directory)
  function readAndReturnNodeFor (tree) {
    if (builder.canceled) {
      var canceled = new SilentError('Build Canceled');
      canceled.wasCanceled = true
      return RSVP.Promise.reject(canceled);
    }


    builder.warnIfNecessary(tree)
    tree = builder.wrapIfNecessary(tree)
    var index = newTreesRead.indexOf(tree)
    if (index !== -1) {

      // Return node from cache to deduplicate `.read`
      if (nodeCache[index].directory == null) {
        // node.directory gets set at the very end, so we have found an as-yet
        // incomplete node. This can happen if there is a cycle.
        throw new Error('Tree cycle detected')
      }
      var cachedNode = nodeCache[index];

      heimdall.start({
        name: getDescription(cachedNode.tree),
        broccoliNode: true,
        broccoliId: cachedNode.id,
        broccoliCachedNode: true,
        broccoliPluginName: getPluginName(cachedNode.tree)
      }).stop();

      return RSVP.Promise.resolve(cachedNode)
    }

    var node = new Node(tree);
    var cookie = heimdall.start({
      name: getDescription(tree),
      broccoliNode: true,
      broccoliId: node.id,
      broccoliCachedNode: false,
      broccoliPluginName: getPluginName(tree)
    });

    node.__heimdall__ = heimdall.current;
    // better to use promises via heimdall.node(description, fn)

    // we don't actually support duplicate trees, as such we should likely tag them..
    // and kill the parallel array structure
    newTreesRead.push(tree)
    nodeCache.push(node)

    var treeDirPromise

    if (typeof tree === 'string') {
      treeDirPromise = RSVP.Promise.resolve()
        .then(function () {
          if (willReadStringTree) willReadStringTree(tree)
          return tree
        })
    } else if (!tree || (typeof tree.read !== 'function' && typeof tree.rebuild !== 'function')) {
      throw new Error('Invalid tree found. You must supply a path or an object with a `.read` (deprecated) or `.rebuild` function: ' + getDescription(tree))
    } else {
      var readTreeRunning = false
      treeDirPromise = RSVP.Promise.resolve()
        .then(function () {
          return tree.read(function readTree (subtree) {
            if (readTreeRunning) {
              throw new Error('Parallel readTree call detected; read trees in sequence, e.g. using https://github.com/joliss/promise-map-series')
            }
            readTreeRunning = true

            return RSVP.Promise.resolve()
              .then(function () {
                return readAndReturnNodeFor(subtree) // recurse
              })
              .then(function (childNode) {
                node.addChild(childNode)
                return childNode.directory
              })
              .finally(function () {
                readTreeRunning = false
              })
          })
        }).catch(function(e) {
          if (typeof e === 'object' && e !== null && e.isSilentError) {
            throw e;
          }

          // because `readAndReturnNodeFor` is recursive
          // it does not make sense to throw multiple build errors,
          // we can just report a failure once.
          if (!builder.isThrown) {
            builder.isThrown = true;
            throw new BroccoliBuildError(e, node)
          }

          throw e
        })
        .then(function (dir) {
          if (readTreeRunning) {
            throw new Error('.read returned before readTree finished')
          }

          return dir
        })
    }

    return treeDirPromise
      .then(function (treeDir) {
        cookie.stop();

        if (treeDir == null) throw new Error(tree + ': .read must return a directory')
        node.directory = treeDir
        return node
      })
  }
}

function cleanupTree(tree) {
  if (typeof tree !== 'string') {
    return tree.cleanup()
  }
}

Builder.prototype._cancel = function () {
  this.canceled = true;
  return this._currentStep || RSVP.Promise.resolve();
};

Builder.prototype.cleanup = function () {
  var builder = this;

  return this._cancel().finally(function() {
    return mapSeries(builder.allTreesRead, cleanupTree)
  }).catch(function(e) {
    if (typeof e === 'object' && e !== null && e.wasCanceled) {
      // if the exception is that we canceled, then cancellation was
      // non-exceptional and we can safely recover
    } else {
      throw e;
    }
  });
}

Builder.prototype.wrapIfNecessary = function (tree) {
  if (typeof tree.rebuild === 'function') {
    // Note: We wrap even if the plugin provides a `.read` function, so that
    // its new `.rebuild` function gets called.
    if (!tree.wrappedTree) { // memoize
      tree.wrappedTree = new apiCompat.NewStyleTreeWrapper(tree)
    }
    return tree.wrappedTree
  } else {
    return tree
  }
}

Builder.prototype.warnIfNecessary = function (tree) {
  if (process.env.BROCCOLI_WARN_READ_API &&
      (typeof tree.read === 'function' || typeof tree.rebuild === 'function') &&
      !tree.__broccoliFeatures__ &&
      !tree.suppressDeprecationWarning) {
    if (!this.didPrintWarningIntro) {
      console.warn('[API] Warning: The .read and .rebuild APIs will stop working in the next Broccoli version')
      console.warn('[API] Warning: Use broccoli-plugin instead: https://github.com/broccolijs/broccoli-plugin')
      this.didPrintWarningIntro = true
    }
    console.warn('[API] Warning: Plugin uses .read/.rebuild API: ' + getDescription(tree))
    tree.suppressDeprecationWarning = true
  }
}


var nodeId = 0

function Node(tree, heimdall) {
  this.id = nodeId++
  this.subtrees = []
  this.tree = tree
  this.parents = []
  this.__heimdall__ = heimdall;
  // mimic information that `broccoliNodeInfo` returns
  this.info = {
    name: getPluginName(tree),
    annotation: getDescription(tree)
  }
}

Node.prototype.addChild = function Node$addChild(child) {
  this.subtrees.push(child)
}

Node.prototype.inspect = function() {
  return 'Node:' + this.id +
    ' subtrees: ' + this.subtrees.length
}

Node.prototype.toJSON = function() {
  var description = getDescription(this.tree)
  var subtrees = this.subtrees.map(function(node) {
    return node.id
  })

  return {
    id: this.id,
    description: description,
    instantiationStack: this.tree._instantiationStack,
    subtrees: subtrees,
  }
}

exports.getPluginName = getPluginName
function getPluginName(tree) {
  // string trees or plain POJO trees don't really have a plugin name
  if (!tree || tree.constructor === String || tree.constructor === Object) {
    return undefined;
  }
  return tree && tree.constructor && tree.constructor !== String && tree.constructor.name
}

exports.getDescription = getDescription
function getDescription (tree) {
  return (tree && tree.annotation) ||
    (tree && tree.description) ||
    getPluginName(tree) ||
    ('' + tree)
}
# Change User Description Committed
#1 23539 jenbottom Adding the basic code for ember test appk, created with 'ember new' command