_build = function( src, dst, opts ) {

  let err;
  if (!src || !src.length) {
    this.err(HMSTATUS.resumeNotFound, {quit: true});
    return null;
  }

  _prep.call(this, src, dst, opts);

  // Load input resumes as JSON...
  const sheetObjects = ResumeFactory.load(src, {
    format: null, objectify: false, quit: true, inner: {
      sort: _opts.sort,
      private: _opts.private
    }
  }
  , this);

  // Explicit check for any resume loading errors...
  const problemSheets = _.filter(sheetObjects, so => so.fluenterror);
  if (problemSheets && problemSheets.length) {
    problemSheets[0].quit = true; // can't go on
    this.err(problemSheets[0].fluenterror, problemSheets[0]);
    return null;
  }

  // Get the collection of raw JSON sheets
  const sheets = sheetObjects.map(r => r.json);

  // Load the theme...
  let theme = null;
  this.stat(HMEVENT.beforeTheme, { theme: _opts.theme });
  try {
    const tFolder = _verifyTheme.call(this, _opts.theme);
    if (tFolder.fluenterror) {
      tFolder.quit = true;
      this.err(tFolder.fluenterror, tFolder);
      return;
    }
    theme = (_opts.themeObj = _loadTheme(tFolder));
    _addFreebieFormats(theme);
  } catch (error) {
    err = error;
    const newEx = {
      fluenterror: HMSTATUS.themeLoad,
      inner: err,
      attempted: _opts.theme,
      quit: true
    };
    this.err(HMSTATUS.themeLoad, newEx);
    return null;
  }

  this.stat(HMEVENT.afterTheme, {theme});

  // Check for invalid outputs...
  const inv = _verifyOutputs.call(this, dst, theme);
  if (inv && inv.length) {
    this.err(HMSTATUS.invalidFormat, {data: inv, theme, quit: true});
    return null;
  }

  //# Merge input resumes, yielding a single source resume...
  let rez = null;
  if (sheets.length > 1) {
    const isFRESH = !sheets[0].basics;
    const mixed = _.any(sheets, function(s) { if (isFRESH) { return s.basics; } else { return !s.basics; } });
    this.stat(HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed });
    if (mixed) {
      this.err(HMSTATUS.mixedMerge);
    }
    rez = _.reduceRight(sheets, ( a, b ) => extend( true, b, a ));
    this.stat(HMEVENT.afterMerge, { r: rez });
  } else {
    rez = sheets[0];
  }

  // Convert the merged source resume to the theme's format, if necessary..
  const orgFormat = rez.basics ? 'JRS' : 'FRESH';
  const toFormat = theme.render ? 'JRS' : 'FRESH';
  if (toFormat !== orgFormat) {
    this.stat(HMEVENT.beforeInlineConvert);
    rez = RConverter[ `to${toFormat}` ]( rez );
    this.stat(HMEVENT.afterInlineConvert, { file: sheetObjects[0].file, fmt: toFormat });
  }

  // Announce the theme
  this.stat(HMEVENT.applyTheme, {r: rez, theme});

  // Load the resume into a FRESHResume or JRSResume object
  _rezObj = new (RTYPES[ toFormat ])().parseJSON( rez, {private: _opts.private} );

  // Expand output resumes...
  const targets = _expand(dst, theme);

  // Run the transformation!
  _.each(targets, function(t) {
    if (this.hasError() && opts.assert) { return { }; }
    t.final = _single.call(this, t, theme, targets);
    if (t.final != null ? t.final.fluenterror : undefined) {
      t.final.quit = opts.assert;
      this.err(t.final.fluenterror, t.final);
    }
  }
  , this);

  const results = {
    sheet: _rezObj,
    targets,
    processed: targets
  };

  if (this.hasError() && !opts.assert) {
    this.reject(results);
  } else if (!this.hasError()) {
    this.resolve(results);
  }

  return results;
}
Given a source resume in FRESH or JRS format, a destination resume path, and a
theme file, generate 0..N resumes in the desired formats.
@param src Path to the source JSON resume file: "rez/resume.json".
@param dst An array of paths to the target resume file(s).
@param opts Generation options.