pp-parachute
Version:
Airdrop JS Applications
184 lines (159 loc) • 6.68 kB
JavaScript
var path = require('path');
var Promise = require('bluebird');
var fastbind = require('../util/fastbind');
var ParachuteBase = require('./parachute_base');
var toArray = require('../util/array').toArray;
var inherits = require('util').inherits;
var Bundler = require('../bundlers/bundler');
var assert = require('../util/assert');
var fs = require('fs');
var vfs = require('vinyl-fs');
var rename = require('gulp-rename');
//todo separate environment public facing api into a new object that has an environment
//An environment is a collection of application configuration that has some smarts about
//how to build itself. Environments can inherit from one another and all environments
//inherit from Application.
var Environment = function (name) {
ParachuteBase.call(this, name);
this.rootPath = null; //root path from which relative paths work
this.buildPromise = null; //promise denoting completion of a build
this.resolveBuildPromise = null; //build promise resolve
this.serveAtUrls = []; //what urls this environment is served at
this.application = null;
this.listening = {};
};
inherits(Environment, ParachuteBase);
Environment.prototype.extends = function(other) {
//todo -- lazy look up
};
//Writes a bundler to disk. All bundlers should resolve a vinyl file. This is what gets written
Environment.prototype.writeBundlerToDisk = function (bundler) {
var self = this;
var write = function (resolve, reject) {
var outputDirectoryRoot = path.join(self.getRootPath(), self.getOutputDirectory());
var bundlerProperties = self.getBundlerById(bundler.bundlerId);
bundler.vinylStream.path = bundlerProperties.mountPath;
var stream = vfs.dest(outputDirectoryRoot);
stream.on('finish', resolve);
//vinylStream can't be an actual vinyl file, it needs to be a stream WITH a vinyl file
bundler.vinylStream.pipe(rename(bundlerProperties.mountPath)).pipe(stream);
};
return new Promise(write).catch(function (e) {
console.log('Write to disk', e.stack);
});
};
//Watch a set of bundles and respond to the events they emit
Environment.prototype.watch = function () {
var bundlers = toArray(this.getActiveBundlers());
this.ensureBuildPromise();
this.emit('buildStarted', bundlers);
for (var i = 0; i < bundlers.length; i++) {
var bundler = bundlers[i];
if (!this.listening[bundler.bundlerId]) {
this.listening[bundler.bundlerId] = true;
bundler.on('bundleStarted', fastbind(this.onBundleStarted, this));
bundler.on('bundleCompleted', fastbind(this.onBundleCompleted, this));
}
bundler.watch().catch(function () {});
}
return this.buildPromise;
};
//build a set of bundlers without watching.
Environment.prototype.build = function () {
var bundlers = toArray(this.getActiveBundlers());
this.ensureBuildPromise();
this.emit('buildStarted', bundlers);
for (var i = 0; i < bundlers.length; i++) {
var bundler = bundlers[i];
if (!this.listening[bundler.bundlerId]) {
bundler.on('bundleStarted', fastbind(this.onBundleStarted, this));
bundler.on('bundleCompleted', fastbind(this.onBundleCompleted, this));
}
bundler.build();
}
return this.buildPromise;
};
//make sure we have a valid build promise. A new build promise is created if we get a call
//to build and we are not currently building.
Environment.prototype.ensureBuildPromise = function () {
var self = this;
if (!this.buildPromise || !this.buildPromise.isPending()) {
this.buildPromise = new Promise(function (resolve) {
self.resolveBuildPromise = resolve;
});
}
};
//handle a bundlers bundleCompleted event, write that bundler to disk and
//if no other bundlers are in progress, complete the build
Environment.prototype.onBundleCompleted = function (bundler) {
this.writeBundlerToDisk(bundler).then(fastbind(function () {
if (!this.buildInProgress()) {
this.finishBuild();
}
}, this)).catch(function (e) {
console.log('WriteBundlerToDisk -- Catch', e);
});
};
//handle a bundler's bundleStarted event. Ensure a valid build promise and
//emit buildStarted if no bundlers are currently building.
Environment.prototype.onBundleStarted = function (bundler) {
this.ensureBuildPromise();
if (!this.buildInProgress()) {
this.emit('buildStarted', this.getActiveBundlers());
}
};
Environment.prototype.buildInProgress = function () {
return toArray(this.getActiveBundlers()).some(function (bundler) {
return bundler.bundleStatus === Bundler.BundleStatus.Bundling;
});
};
Environment.prototype.getFailedBundles = function () {
return toArray(this.getActiveBundlers()).filter(function (bundler) {
return bundler.bundleStatus === Bundler.BundleStatus.Failed;
});
};
Environment.prototype.getPassedBundles = function () {
return toArray(this.getActiveBundlers()).filter(function (bundler) {
return bundler.bundleStatus === Bundler.BundleStatus.Succeeded;
});
};
//emits build events if they have not yet been emitted
Environment.prototype.finishBuild = function (buildStartTime) {
//this might get called twice, make sure it doesn't emit two events
if (this.buildPromise.isPending()) {
var passedBundles = this.getPassedBundles();
var failedBundles = this.getFailedBundles();
var buildSucceeded = failedBundles.length === 0;
var buildDuration = new Date().getTime() - buildStartTime;
if (buildSucceeded) {
this.emit('buildSucceeded', passedBundles, buildDuration);
this.resolveBuildPromise(true);
} else {
this.emit('buildFailed', failedBundles, buildDuration);
this.resolveBuildPromise(false);
}
this.emit('buildCompleted', passedBundles, failedBundles, buildDuration);
}
return this.buildPromise;
};
Environment.prototype.serveAtUrl = function (url) {
assert.isString(url);
if (url.charAt(url.length - 1) === '/') {
url = url.substring(0, url.length - 1);
}
this.serveAtUrls.push(url);
return this;
};
Environment.prototype.getServedUrls = function (url) {
return this.serveAtUrls.slice(0);
};
Environment.prototype.getApplication = function() {
return this.application;
};
//shut down all assetWatchers from the bundlers
Environment.prototype.shutdown = function () {
toArray(this.getActiveBundlers()).forEach(function (bundler) {
bundler.shutdown();
});
};
module.exports = Environment;