<commit_before>/*
 * RPC-API to manipulate json documents on the server. These documents
 * are stored in a mongodb database.
 */

var db = require("./db");
var util  =require("./util");
var time = require("./time");
var async = require("async");
var CONST = require("./constants");

var EventEmitter = require("events").EventEmitter,
	emitter = new EventEmitter();

/*
 * Register listeners to the rpc-api events so other modules can listen to what happens
 * to this module.
 *
 */
exports.on = function( ev_type, ret_handler ){

	emitter.on(ev_type, function( ctx, rcpts ){

		ret_handler( {
						ev_type:ev_type,
						ev_tstamp: time.now(),
						ev_ctx:ctx
					 }, rcpts );
	});
}

/*
 * emit an event as it was generated inside the api.
 */
var emit = exports.emit = function( ev_type, ctx, rcpts ){

	emitter.emit(ev_type, ctx, rcpts );
}

/*
 * Namespace for public functions: all functions in the remote namespace
 * will be accessible as an rpc procedure to remote callers and made public to the
 * front module (server.js) to local callers: $mod_name.api.docs.$proc_name(params, ret_handler);
 */

exports.remote = {

	/*
	 * Create a new document.
	 */
	create : function( ctx, ret_handler ){

		ctx.doc = util.clone(ctx.params.doc);
		ctx.doc.uid = ctx.params.uid;
		ctx.doc.catalog = ctx.params.catalog;
		ctx.doc.rcpts = ctx.params.rcpts;
		ctx.doc.ctime = time.now();
		if(ctx.params.ttl)
			ctx.doc.etime = ctx.doc.ctime + ctx.params.ttl * CONST.MILLIS_IN_A_SECOND();

		db.save(ctx.doc.catalog, ctx.doc, function(err){

			if(!err){

				ctx.doc.wid = "" + ctx.doc._id;
				delete ctx.doc._id;
				if(ctx.config.emit){

					ctx.payload = ctx.doc;
					emit( "ev_api_create", ctx );
				}

				ctx.config.save = CONST.DISABLE();
				ret_handler(null, ctx.doc);

				if(ctx.doc.etime){

						time.create_remove_timer( {tid:ctx.doc.wid, etime:ctx.doc.etime, data:{catalog:ctx.params.catalog}} );
				}
			}else
				ret_handler(err,null);
		});

	},

	/*
	 * Delete a document by id
	 */
	dispose : function(ctx, ret_handler){

		ctx.config.save = CONST.DISABLE();
		db.removeById( ctx.params.catalog, ctx.params.wid, function(err, val){

			if(!err){
				if(ctx.config.emit){

					ctx.payload = ctx.params;
					emit( "ev_api_dispose", ctx );
				}
				ret_handler(null,CONST.OK());
			}else
				ret_handler(err,null);

		});

	},

	/*
	 * Add a recipient by uid to the document's rcpts list.
	 */
	join : function( ctx, ret_handler ){

		ctx.config.save = CONST.DISABLE();

		if(ctx.doc.rcpts.indexOf(ctx.params.uid) == -1 ){

			ctx.doc.rcpts.push(ctx.params.uid);
			db.save(ctx.doc.catalog, ctx.doc, function(err){

				if(!err){

					ctx.doc.wid = "" + ctx.doc._id;
					delete ctx.doc._id;
					if(ctx.config.emit){

						ctx.payload = ctx.params;
						emit( "ev_api_join", ctx );
					}
					ret_handler(null, ctx.doc);

				}else
					ret_handler(err,null);
			});

		}else{

			ret_handler(null,CONST.OK());
		}

	},

	/*
	 * Remove a recipient by uid from the document's rcpts list.
	 */
	unjoin : function( ctx, ret_handler ){

		ctx.doc.rcpts.splice(ctx.doc.rcpts.indexOf(ctx.params.uid),1); //remove uid from rcpts list.
		ret_handler(null,CONST.OK());

	},

	/*
	 * Remove a field from the document.
	 */
	remove : function(ctx, ret_handler){

		util.del_field(ctx.doc, ctx.params.fname );
		ret_handler(null,CONST.OK());

	},

	/*
	 * Set a value to the field or create it if not exists.
	 */
	set : function( ctx, ret_handler){

		util.set_field(ctx.doc, ctx.params.fname, ctx.params.value );
		ret_handler(null,CONST.OK());

	},

	/*
	 * Push a value to an array field.
	 */
	push : function(ctx, ret_handler){

			var arr = util.get_field(ctx.doc,ctx.params.fname);
			arr.push(ctx.params.value);
			ret_handler(null,CONST.OK());

	},

	/*
	 * Pops the last value of the array field in the document.
	 */
	pop : function(ctx, ret_handler){

			var arr = util.get_field(ctx.doc,ctx.params.fname);
			arr.pop();
			ret_handler(null,CONST.OK());

	},

	/*
	 * Pops the first value of the array field in the document.
	 */
	shift : function(ctx, ret_handler){

				var arr = util.get_field(ctx.doc,ctx.params.fname);
				arr.shift();
				ret_handler(null,CONST.OK());

	},

	/*
	 * events ack procedure
	 */
	ack : function(params, ret_handler){

		if( !(params.uid && params.tstamp) ){

			ret_handler({code:-2, message:"Missing parameters:{uid:,tstamp:}"},null);
			return;
		}

		emit("ev_api_ack", params);

		if(ret_handler)
			ret_handler(null,0);

	},

	/*
	 * get a document or subfield
	 */
	get: function(ctx, ret_handler){

		ctx.config.save = CONST.DISABLE();

		if(ctx.params.fname){

			ret_handler(null, util.get_field(ctx.doc, ctx.params.fname));
		}else{

			ctx.doc.wid = "" + ctx.doc._id;
			delete ctx.doc._id;
			ret_handler(null, ctx.doc);
		}
	}

}

<commit_msg>minor formatting change.<commit_after>/*
 * RPC-API to manipulate json documents on the server. These documents
 * are stored in a mongodb database.
 */

var db = require("./db");
var util  =require("./util");
var time = require("./time");
var async = require("async");
var CONST = require("./constants");

var EventEmitter = require("events").EventEmitter,
	emitter = new EventEmitter();

/*
 * Register listeners to the rpc-api events so other modules can listen to what happens
 * to this module.
 *
 */
exports.on = function( ev_type, ret_handler ){

	emitter.on(ev_type, function( ctx, rcpts ){

		ret_handler( {
						ev_type:ev_type,
						ev_tstamp: time.now(),
						ev_ctx:ctx
					 }, rcpts );
	});
}

/*
 * emit an event as it was generated inside the api.
 */
var emit = exports.emit = function( ev_type, ctx, rcpts ){

	emitter.emit(ev_type, ctx, rcpts );
}

/*
 * Namespace for public functions: all functions in the remote namespace
 * will be accessible as an rpc procedure to remote callers and made public to the
 * front module (server.js) to local callers: $mod_name.api.docs.$proc_name(params, ret_handler);
 */

exports.remote = {

	/*
	 * Create a new document.
	 */
	create : function( ctx, ret_handler ){

		ctx.doc = util.clone(ctx.params.doc);
		ctx.doc.uid = ctx.params.uid;
		ctx.doc.catalog = ctx.params.catalog;
		ctx.doc.rcpts = ctx.params.rcpts;
		ctx.doc.ctime = time.now();
		if(ctx.params.ttl)
			ctx.doc.etime = ctx.doc.ctime + ctx.params.ttl * CONST.MILLIS_IN_A_SECOND();

		db.save(ctx.doc.catalog, ctx.doc, function(err){

			if(!err){

				ctx.doc.wid = "" + ctx.doc._id;
				delete ctx.doc._id;
				if(ctx.config.emit){

					ctx.payload = ctx.doc;
					emit( "ev_api_create", ctx );
				}

				ctx.config.save = CONST.DISABLE();
				ret_handler(null, ctx.doc);

				if(ctx.doc.etime){

						time.create_remove_timer( {tid:ctx.doc.wid, etime:ctx.doc.etime, data:{catalog:ctx.params.catalog}} );
				}
			}else
				ret_handler(err,null);
		});

	},

	/*
	 * Delete a document by id
	 */
	dispose : function(ctx, ret_handler){

		ctx.config.save = CONST.DISABLE();
		db.removeById( ctx.params.catalog, ctx.params.wid, function(err, val){

			if(!err){
				if(ctx.config.emit){

					ctx.payload = ctx.params;
					emit( "ev_api_dispose", ctx );
				}
				ret_handler(null,CONST.OK());
			}else
				ret_handler(err,null);

		});

	},

	/*
	 * Add a recipient by uid to the document's rcpts list.
	 */
	join : function( ctx, ret_handler ){

		ctx.config.save = CONST.DISABLE();

		if(ctx.doc.rcpts.indexOf(ctx.params.uid) == -1 ){

			ctx.doc.rcpts.push(ctx.params.uid);
			db.save(ctx.doc.catalog, ctx.doc, function(err){

				if(!err){

					ctx.doc.wid = "" + ctx.doc._id;
					delete ctx.doc._id;
					if(ctx.config.emit){

						ctx.payload = ctx.params;
						emit( "ev_api_join", ctx );
					}
					ret_handler(null, ctx.doc);

				}else
					ret_handler(err,null);
			});

		}else{

			ret_handler(null,CONST.OK());
		}

	},

	/*
	 * Remove a recipient by uid from the document's rcpts list.
	 */
	unjoin : function( ctx, ret_handler ){

		ctx.doc.rcpts.splice(ctx.doc.rcpts.indexOf(ctx.params.uid),1); //remove uid from rcpts list.
		ret_handler(null,CONST.OK());

	},

	/*
	 * Remove a field from the document.
	 */
	remove : function(ctx, ret_handler){

		util.del_field(ctx.doc, ctx.params.fname );
		ret_handler(null,CONST.OK());

	},

	/*
	 * Set a value to the field or create it if not exists.
	 */
	set : function( ctx, ret_handler){

		util.set_field(ctx.doc, ctx.params.fname, ctx.params.value );
		ret_handler(null,CONST.OK());

	},

	/*
	 * Push a value to an array field.
	 */
	push : function(ctx, ret_handler){

			var arr = util.get_field(ctx.doc,ctx.params.fname);
			arr.push(ctx.params.value);
			ret_handler(null,CONST.OK());

	},

	/*
	 * Pops the last value of the array field in the document.
	 */
	pop : function(ctx, ret_handler){

			var arr = util.get_field(ctx.doc,ctx.params.fname);
			arr.pop();
			ret_handler(null,CONST.OK());

	},

	/*
	 * Pops the first value of the array field in the document.
	 */
	shift : function(ctx, ret_handler){

				var arr = util.get_field(ctx.doc,ctx.params.fname);
				arr.shift();
				ret_handler(null,CONST.OK());

	},

	/*
	 * events ack procedure
	 */
	ack : function(params, ret_handler){

		if( !(params.uid && params.tstamp) ){

			ret_handler({code:-2, message:"Missing parameters:{uid:,tstamp:}"},null);
			return;
		}

		emit("ev_api_ack", params);

		if(ret_handler)
			ret_handler(null,0);

	},

	/*
	 * get a document or subfield
	 */
	get: function(ctx, ret_handler){

		ctx.config.save = CONST.DISABLE();

		if(ctx.params.fname){

			ret_handler(null, util.get_field(ctx.doc, ctx.params.fname));
		}else{

			ctx.doc.wid = "" + ctx.doc._id;
			delete ctx.doc._id;
			ret_handler(null, ctx.doc);
		}
	}

}

<|endoftext|>


<commit_before>import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import process from 'node:process';

import arrify from 'arrify';
import chunkd from 'chunkd';
import commonPathPrefix from 'common-path-prefix';
import Emittery from 'emittery';
import debounce from 'lodash/debounce.js';
import ms from 'ms';
import pMap from 'p-map';
import resolveCwd from 'resolve-cwd';
import tempDir from 'temp-dir';

import fork from './fork.js';
import * as globs from './globs.js';
import isCi from './is-ci.js';
import {getApplicableLineNumbers} from './line-numbers.js';
import {observeWorkerProcess} from './plugin-support/shared-workers.js';
import RunStatus from './run-status.js';
import scheduler from './scheduler.js';
import serializeError from './serialize-error.js';

function resolveModules(modules) {
	return arrify(modules).map(name => {
		const modulePath = resolveCwd.silent(name);

		if (modulePath === undefined) {
			throw new Error(`Could not resolve required module ’${name}’`);
		}

		return modulePath;
	});
}

function getFilePathPrefix(files) {
	if (files.length === 1) {
		// Get the correct prefix up to the basename.
		return commonPathPrefix([files[0], path.dirname(files[0])]);
	}

	return commonPathPrefix(files);
}

export default class Api extends Emittery {
	constructor(options) {
		super();

		this.options = {match: [], moduleTypes: {}, ...options};
		this.options.require = resolveModules(this.options.require);

		this._cacheDir = null;
		this._interruptHandler = () => {};

		if (options.ranFromCli) {
			process.on('SIGINT', () => this._interruptHandler());
		}
	}

	async run({files: selectedFiles = [], filter = [], runtimeOptions = {}} = {}) { // eslint-disable-line complexity
		let setupOrGlobError;

		const apiOptions = this.options;

		// Each run will have its own status. It can only be created when test files
		// have been found.
		let runStatus;
		// Irrespectively, perform some setup now, before finding test files.

		// Track active forks and manage timeouts.
		const failFast = apiOptions.failFast === true;
		let bailed = false;
		const pendingWorkers = new Set();
		const timedOutWorkerFiles = new Set();
		let restartTimer;
		let ignoreTimeoutsUntil = 0;
		if (apiOptions.timeout && !apiOptions.debug) {
			const timeout = ms(apiOptions.timeout);

			restartTimer = debounce(() => {
				if (Date.now() < ignoreTimeoutsUntil) {
					restartTimer();
					return;
				}

				// If failFast is active, prevent new test files from running after
				// the current ones are exited.
				if (failFast) {
					bailed = true;
				}

				runStatus.emitStateChange({type: 'timeout', period: timeout});

				for (const worker of pendingWorkers) {
					timedOutWorkerFiles.add(worker.file);
					worker.exit();
				}
			}, timeout);
		} else {
			restartTimer = Object.assign(() => {}, {cancel() {}});
		}

		this._interruptHandler = () => {
			if (bailed) {
				// Exiting already
				return;
			}

			// Prevent new test files from running
			bailed = true;

			// Make sure we don't run the timeout handler
			restartTimer.cancel();

			runStatus.emitStateChange({type: 'interrupt'});

			for (const worker of pendingWorkers) {
				worker.exit();
			}
		};

		let testFiles;
		try {
			testFiles = await globs.findTests({cwd: this.options.projectDir, ...apiOptions.globs});
			if (selectedFiles.length === 0) {
				selectedFiles = filter.length === 0 ? testFiles : globs.applyTestFileFilter({
					cwd: this.options.projectDir,
					filter: filter.map(({pattern}) => pattern),
					testFiles,
				});
			}
		} catch (error) {
			selectedFiles = [];
			setupOrGlobError = error;
		}

		try {
			if (this.options.parallelRuns) {
				const {currentIndex, totalRuns} = this.options.parallelRuns;
				const fileCount = selectedFiles.length;

				// The files must be in the same order across all runs, so sort them.
				selectedFiles = selectedFiles.sort((a, b) => a.localeCompare(b, [], {numeric: true}));
				selectedFiles = chunkd(selectedFiles, currentIndex, totalRuns);

				const currentFileCount = selectedFiles.length;

				runStatus = new RunStatus(fileCount, {currentFileCount, currentIndex, totalRuns});
			} else {
				runStatus = new RunStatus(selectedFiles.length, null);
			}

			selectedFiles = scheduler.failingTestsFirst(selectedFiles, this._getLocalCacheDir(), this.options.cacheEnabled);

			const debugWithoutSpecificFile = Boolean(this.options.debug) && !this.options.debug.active && selectedFiles.length !== 1;

			await this.emit('run', {
				bailWithoutReporting: debugWithoutSpecificFile,
				clearLogOnNextRun: runtimeOptions.clearLogOnNextRun === true,
				debug: Boolean(this.options.debug),
				failFastEnabled: failFast,
				filePathPrefix: getFilePathPrefix(selectedFiles),
				files: selectedFiles,
				matching: apiOptions.match.length > 0,
				previousFailures: runtimeOptions.previousFailures || 0,
				runOnlyExclusive: runtimeOptions.runOnlyExclusive === true,
				runVector: runtimeOptions.runVector || 0,
				status: runStatus,
			});

			if (setupOrGlobError) {
				throw setupOrGlobError;
			}

			// Bail out early if no files were found, or when debugging and there is not a single specific test file to debug.
			if (selectedFiles.length === 0 || debugWithoutSpecificFile) {
				return runStatus;
			}

			runStatus.on('stateChange', record => {
				if (record.testFile && !timedOutWorkerFiles.has(record.testFile)) {
					// Restart the timer whenever there is activity from workers that
					// haven't already timed out.
					restartTimer();
				}

				if (failFast && (record.type === 'hook-failed' || record.type === 'test-failed' || record.type === 'worker-failed')) {
					// Prevent new test files from running once a test has failed.
					bailed = true;

					// Try to stop currently scheduled tests.
					for (const worker of pendingWorkers) {
						worker.notifyOfPeerFailure();
					}
				}
			});

			const {providers = []} = this.options;
			const providerStates = (await Promise.all(providers.map(async ({type, main}) => {
				const state = await main.compile({cacheDir: this._createCacheDir(), files: testFiles});
				return state === null ? null : {type, state};
			}))).filter(state => state !== null);

			// Resolve the correct concurrency value.
			let concurrency = Math.min(os.cpus().length, isCi ? 2 : Number.POSITIVE_INFINITY);
			if (apiOptions.concurrency > 0) {
				concurrency = apiOptions.concurrency;
			}

			if (apiOptions.serial) {
				concurrency = 1;
			}

			const deregisteredSharedWorkers = [];

			// Try and run each file, limited by `concurrency`.
			await pMap(selectedFiles, async file => {
				// No new files should be run once a test has timed out or failed,
				// and failFast is enabled.
				if (bailed) {
					return;
				}

				const lineNumbers = getApplicableLineNumbers(globs.normalizeFileForMatching(apiOptions.projectDir, file), filter);
				// Removing `providers` field because they cannot be transfered to the worker threads.
				const {providers, ...forkOptions} = apiOptions;
				const options = {
					...forkOptions,
					providerStates,
					lineNumbers,
					recordNewSnapshots: !isCi,
					// If we're looking for matches, run every single test process in exclusive-only mode
					runOnlyExclusive: apiOptions.match.length > 0 || runtimeOptions.runOnlyExclusive === true,
				};

				if (runtimeOptions.updateSnapshots) {
					// Don't use in Object.assign() since it'll override options.updateSnapshots even when false.
					options.updateSnapshots = true;
				}

				const worker = fork(file, options, apiOptions.nodeArguments);
				worker.onStateChange(data => {
					if (data.type === 'test-timeout-configured' && !apiOptions.debug) {
						ignoreTimeoutsUntil = Math.max(ignoreTimeoutsUntil, Date.now() + data.period);
					}
				});
				runStatus.observeWorker(worker, file, {selectingLines: lineNumbers.length > 0});
				deregisteredSharedWorkers.push(observeWorkerProcess(worker, runStatus));

				pendingWorkers.add(worker);
				worker.promise.then(() => {
					pendingWorkers.delete(worker);
				});
				restartTimer();

				await worker.promise;
			}, {concurrency, stopOnError: false});

			// Allow shared workers to clean up before the run ends.
			await Promise.all(deregisteredSharedWorkers);
			scheduler.storeFailedTestFiles(runStatus, this.options.cacheEnabled === false ? null : this._createCacheDir());
		} catch (error) {
			if (error && error.name === 'AggregateError') {
				for (const error_ of error) {
					runStatus.emitStateChange({type: 'internal-error', err: serializeError('Internal error', false, error_)});
				}
			} else {
				runStatus.emitStateChange({type: 'internal-error', err: serializeError('Internal error', false, error)});
			}
		}

		restartTimer.cancel();
		return runStatus;
	}

	_getLocalCacheDir() {
		return path.join(this.options.projectDir, 'node_modules', '.cache', 'ava');
	}

	_createCacheDir() {
		if (this._cacheDir) {
			return this._cacheDir;
		}

		const cacheDir = this.options.cacheEnabled === false
			? fs.mkdtempSync(`${tempDir}${path.sep}`)
			: this._getLocalCacheDir();

		// Ensure cacheDir exists
		fs.mkdirSync(cacheDir, {recursive: true});

		this._cacheDir = cacheDir;

		return cacheDir;
	}
}
<commit_msg>Fix iterating over p-map aggregate errors<commit_after>import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import process from 'node:process';

import arrify from 'arrify';
import chunkd from 'chunkd';
import commonPathPrefix from 'common-path-prefix';
import Emittery from 'emittery';
import debounce from 'lodash/debounce.js';
import ms from 'ms';
import pMap from 'p-map';
import resolveCwd from 'resolve-cwd';
import tempDir from 'temp-dir';

import fork from './fork.js';
import * as globs from './globs.js';
import isCi from './is-ci.js';
import {getApplicableLineNumbers} from './line-numbers.js';
import {observeWorkerProcess} from './plugin-support/shared-workers.js';
import RunStatus from './run-status.js';
import scheduler from './scheduler.js';
import serializeError from './serialize-error.js';

function resolveModules(modules) {
	return arrify(modules).map(name => {
		const modulePath = resolveCwd.silent(name);

		if (modulePath === undefined) {
			throw new Error(`Could not resolve required module ’${name}’`);
		}

		return modulePath;
	});
}

function getFilePathPrefix(files) {
	if (files.length === 1) {
		// Get the correct prefix up to the basename.
		return commonPathPrefix([files[0], path.dirname(files[0])]);
	}

	return commonPathPrefix(files);
}

export default class Api extends Emittery {
	constructor(options) {
		super();

		this.options = {match: [], moduleTypes: {}, ...options};
		this.options.require = resolveModules(this.options.require);

		this._cacheDir = null;
		this._interruptHandler = () => {};

		if (options.ranFromCli) {
			process.on('SIGINT', () => this._interruptHandler());
		}
	}

	async run({files: selectedFiles = [], filter = [], runtimeOptions = {}} = {}) { // eslint-disable-line complexity
		let setupOrGlobError;

		const apiOptions = this.options;

		// Each run will have its own status. It can only be created when test files
		// have been found.
		let runStatus;
		// Irrespectively, perform some setup now, before finding test files.

		// Track active forks and manage timeouts.
		const failFast = apiOptions.failFast === true;
		let bailed = false;
		const pendingWorkers = new Set();
		const timedOutWorkerFiles = new Set();
		let restartTimer;
		let ignoreTimeoutsUntil = 0;
		if (apiOptions.timeout && !apiOptions.debug) {
			const timeout = ms(apiOptions.timeout);

			restartTimer = debounce(() => {
				if (Date.now() < ignoreTimeoutsUntil) {
					restartTimer();
					return;
				}

				// If failFast is active, prevent new test files from running after
				// the current ones are exited.
				if (failFast) {
					bailed = true;
				}

				runStatus.emitStateChange({type: 'timeout', period: timeout});

				for (const worker of pendingWorkers) {
					timedOutWorkerFiles.add(worker.file);
					worker.exit();
				}
			}, timeout);
		} else {
			restartTimer = Object.assign(() => {}, {cancel() {}});
		}

		this._interruptHandler = () => {
			if (bailed) {
				// Exiting already
				return;
			}

			// Prevent new test files from running
			bailed = true;

			// Make sure we don't run the timeout handler
			restartTimer.cancel();

			runStatus.emitStateChange({type: 'interrupt'});

			for (const worker of pendingWorkers) {
				worker.exit();
			}
		};

		let testFiles;
		try {
			testFiles = await globs.findTests({cwd: this.options.projectDir, ...apiOptions.globs});
			if (selectedFiles.length === 0) {
				selectedFiles = filter.length === 0 ? testFiles : globs.applyTestFileFilter({
					cwd: this.options.projectDir,
					filter: filter.map(({pattern}) => pattern),
					testFiles,
				});
			}
		} catch (error) {
			selectedFiles = [];
			setupOrGlobError = error;
		}

		try {
			if (this.options.parallelRuns) {
				const {currentIndex, totalRuns} = this.options.parallelRuns;
				const fileCount = selectedFiles.length;

				// The files must be in the same order across all runs, so sort them.
				selectedFiles = selectedFiles.sort((a, b) => a.localeCompare(b, [], {numeric: true}));
				selectedFiles = chunkd(selectedFiles, currentIndex, totalRuns);

				const currentFileCount = selectedFiles.length;

				runStatus = new RunStatus(fileCount, {currentFileCount, currentIndex, totalRuns});
			} else {
				runStatus = new RunStatus(selectedFiles.length, null);
			}

			selectedFiles = scheduler.failingTestsFirst(selectedFiles, this._getLocalCacheDir(), this.options.cacheEnabled);

			const debugWithoutSpecificFile = Boolean(this.options.debug) && !this.options.debug.active && selectedFiles.length !== 1;

			await this.emit('run', {
				bailWithoutReporting: debugWithoutSpecificFile,
				clearLogOnNextRun: runtimeOptions.clearLogOnNextRun === true,
				debug: Boolean(this.options.debug),
				failFastEnabled: failFast,
				filePathPrefix: getFilePathPrefix(selectedFiles),
				files: selectedFiles,
				matching: apiOptions.match.length > 0,
				previousFailures: runtimeOptions.previousFailures || 0,
				runOnlyExclusive: runtimeOptions.runOnlyExclusive === true,
				runVector: runtimeOptions.runVector || 0,
				status: runStatus,
			});

			if (setupOrGlobError) {
				throw setupOrGlobError;
			}

			// Bail out early if no files were found, or when debugging and there is not a single specific test file to debug.
			if (selectedFiles.length === 0 || debugWithoutSpecificFile) {
				return runStatus;
			}

			runStatus.on('stateChange', record => {
				if (record.testFile && !timedOutWorkerFiles.has(record.testFile)) {
					// Restart the timer whenever there is activity from workers that
					// haven't already timed out.
					restartTimer();
				}

				if (failFast && (record.type === 'hook-failed' || record.type === 'test-failed' || record.type === 'worker-failed')) {
					// Prevent new test files from running once a test has failed.
					bailed = true;

					// Try to stop currently scheduled tests.
					for (const worker of pendingWorkers) {
						worker.notifyOfPeerFailure();
					}
				}
			});

			const {providers = []} = this.options;
			const providerStates = (await Promise.all(providers.map(async ({type, main}) => {
				const state = await main.compile({cacheDir: this._createCacheDir(), files: testFiles});
				return state === null ? null : {type, state};
			}))).filter(state => state !== null);

			// Resolve the correct concurrency value.
			let concurrency = Math.min(os.cpus().length, isCi ? 2 : Number.POSITIVE_INFINITY);
			if (apiOptions.concurrency > 0) {
				concurrency = apiOptions.concurrency;
			}

			if (apiOptions.serial) {
				concurrency = 1;
			}

			const deregisteredSharedWorkers = [];

			// Try and run each file, limited by `concurrency`.
			await pMap(selectedFiles, async file => {
				// No new files should be run once a test has timed out or failed,
				// and failFast is enabled.
				if (bailed) {
					return;
				}

				const lineNumbers = getApplicableLineNumbers(globs.normalizeFileForMatching(apiOptions.projectDir, file), filter);
				// Removing `providers` field because they cannot be transfered to the worker threads.
				const {providers, ...forkOptions} = apiOptions;
				const options = {
					...forkOptions,
					providerStates,
					lineNumbers,
					recordNewSnapshots: !isCi,
					// If we're looking for matches, run every single test process in exclusive-only mode
					runOnlyExclusive: apiOptions.match.length > 0 || runtimeOptions.runOnlyExclusive === true,
				};

				if (runtimeOptions.updateSnapshots) {
					// Don't use in Object.assign() since it'll override options.updateSnapshots even when false.
					options.updateSnapshots = true;
				}

				const worker = fork(file, options, apiOptions.nodeArguments);
				worker.onStateChange(data => {
					if (data.type === 'test-timeout-configured' && !apiOptions.debug) {
						ignoreTimeoutsUntil = Math.max(ignoreTimeoutsUntil, Date.now() + data.period);
					}
				});
				runStatus.observeWorker(worker, file, {selectingLines: lineNumbers.length > 0});
				deregisteredSharedWorkers.push(observeWorkerProcess(worker, runStatus));

				pendingWorkers.add(worker);
				worker.promise.then(() => {
					pendingWorkers.delete(worker);
				});
				restartTimer();

				await worker.promise;
			}, {concurrency, stopOnError: false});

			// Allow shared workers to clean up before the run ends.
			await Promise.all(deregisteredSharedWorkers);
			scheduler.storeFailedTestFiles(runStatus, this.options.cacheEnabled === false ? null : this._createCacheDir());
		} catch (error) {
			if (error && error.name === 'AggregateError') {
				for (const error_ of error.errors) {
					runStatus.emitStateChange({type: 'internal-error', err: serializeError('Internal error', false, error_)});
				}
			} else {
				runStatus.emitStateChange({type: 'internal-error', err: serializeError('Internal error', false, error)});
			}
		}

		restartTimer.cancel();
		return runStatus;
	}

	_getLocalCacheDir() {
		return path.join(this.options.projectDir, 'node_modules', '.cache', 'ava');
	}

	_createCacheDir() {
		if (this._cacheDir) {
			return this._cacheDir;
		}

		const cacheDir = this.options.cacheEnabled === false
			? fs.mkdtempSync(`${tempDir}${path.sep}`)
			: this._getLocalCacheDir();

		// Ensure cacheDir exists
		fs.mkdirSync(cacheDir, {recursive: true});

		this._cacheDir = cacheDir;

		return cacheDir;
	}
}
<|endoftext|>


<commit_before>var http = require('http'),
    https = require('https'),
    fs = require('fs'),
    path = require('path'),
    zlib = require('zlib'),
    tar = require('tar'),
    url = require('url'),
    prompt = require('prompt'),
    open = require('open'),
    bitballoon = require('./bitballoon'),
    auth = require('./auth'),
    api = require('./api');

var supportedTemplates = {
  angular: {
    url: 'https://codeload.github.com/firebase/angularFire-seed/' +
              'legacy.tar.gz/master',
    settings: {
      'public': 'app',
      'rules': 'config/security-rules.json',
    },
    config: 'app/js/config.js',
    configRegex: /https:\/\/INSTANCE\.firebaseio\.com/,
    completedMessage: 'BOOTSTRAPPING SUCCESSFUL'.green + ' - Instructions and' +
                        ' setup guide here: ' +
                        'https://github.com/firebase/angularFire-seed'.cyan
  }
};

var defaultSettings = {
  'public': '.'
}

module.exports = {
  init: function(argv) {
    auth.requireLogin(function(err) {
      if (err) {
        console.log('LOGIN ERROR'.red);
        process.exit(1);
      }
      auth.getFirebases(function(err, firebases) {
        if (err) {
          console.log('INITIALIZATION ERROR'.red);
          process.exit(1);
        }
        var settingsFile = path.resolve('./firebase.json');
        if (fs.existsSync(settingsFile)) {
          console.log('INITIALIZATION ERROR'.red + ' - Directory alread ' +
                        'initialized');
          process.exit(1);
        }
        if (firebases.length == 0) {
          console.log('NO FIREBASES'.red + ' - You need to create a Firebase ' +
                        'to initialize the app with at https://firebase.com');
          process.exit(1);
        }
        // Firebase names always a subset of ^[0-9a-z-]*$ so safe to regex
        var pattern = new RegExp('^(' + firebases.join('|') + ')$');
        if (!argv.firebase ||
            (typeof(argv.firebase) !== 'string') ||
            !argv.firebase.match(pattern)) {
          console.log('Choose A Firebase\n' +
                      '---------- YOUR FIREBASES ----------\n'.green +
                      firebases.join('\n') +
                      '\n------------------------------------'.green);
        }
        var schema = {
              properties: {
                firebase: {
                  required: true,
                  pattern: pattern,
                  description: 'Firebase:',
                  type: 'string'
                },
                'public': {
                  required: true,
                  description: 'Public Directory:',
                  default: defaultSettings['public'],
                  type: 'string'
                },
                rules: {
                  description: 'Security Rules File: (none)',
                  type: 'string'
                }
              }
            };
        prompt.get(schema, function(err, result) {
          if (err) {
            console.log('USER INPUT ERROR'.red);
            process.exit(1);
          }
          if (path.relative('.', result['public']).match(/^\./)) {
            console.log('PUBLIC DIRECTORY ERROR'.red + ' - Public directory ' +
                                  'must be within current working directory');
            process.exit(1);
          }
          if (!fs.existsSync(result['public'])) {
            console.log('PUBLIC DIRECTORY ERROR'.red + ' - Public directory ' +
                                  'does not exist');
            process.exit(1);
          }
          var settings = {
            firebase: result.firebase,
            'public': result['public']
          };
          if (result.rules) {
            if (fs.existsSync(result.rules) &&
                fs.statSync(result.rules).isFile()) {
              settings.rules = result.rules;
            } else {
              console.log('INITIALIZATION ERROR'.red + ' - rules file does ' +
                                    'not exist');
              process.exit(1);
            }
          }
          console.log('Initializing app into current directory');
          auth.checkCanAccess(result.firebase, function(err) {
            if (err) {
              console.log('PERMISSION ERROR'.red + ' - You do not have ' +
                                    'permission to use this Firebase');
              process.exit(1);
            }
            console.log('Writing firebase.json settings file');
            var settingsJSON = JSON.stringify(settings, null, 2) + "\n";
            try {
              fs.writeFileSync(settingsFile, settingsJSON);
              console.log('SUCCESSFULLY INITIALIZED APP'.green);
            } catch (err) {
              console.log('INITIALIZATION ERROR'.red);
              process.exit(1);
            }
          });
        });
      });
    });
  },
  bootstrap: function(argv) {
    auth.requireLogin(function(err) {
      if (err) {
        console.log('LOGIN ERROR'.red);
        process.exit(1);
      }
      auth.getFirebases(function(err, firebases) {
        if (err) {
          console.log('INITIALIZATION ERROR'.red);
          process.exit(1);
        }
        if (firebases.length == 0) {
          console.log('NO FIREBASES'.red + ' - You need to create a Firebase ' +
                        'to bootstrap the app with at https://firebase.com');
          process.exit(1);
        }
        // Firebase names always a subset of ^[0-9a-z-]*$ so safe to regex
        var firebasePattern = new RegExp('^(' + firebases.join('|') + ')$');
        var templatePattern = new RegExp('^(' +
                          Object.keys(supportedTemplates).join('|') + ')$');
        if (!argv.firebase ||
            (typeof(argv.firebase) !== 'string') ||
            !argv.firebase.match(firebasePattern)) {
          console.log('Choose A Firebase\n' +
                      '---------- YOUR FIREBASES ----------\n'.green +
                      firebases.join('\n') +
                      '\n------------------------------------'.green);
        }
        var schema = {
              properties: {
                firebase: {
                  required: true,
                  pattern: firebasePattern,
                  description: 'Firebase:',
                  type: 'string'
                }
              }
            };
        prompt.get(schema, function(err, result) {
          if (err) {
            console.log('USER INPUT ERROR'.red);
            process.exit(1);
          }
          var firebase = result.firebase;
          if (!argv.template ||
              (typeof(argv.template) !== 'string') ||
              !argv.template.match(templatePattern)) {
            console.log('Choose A Template\n' +
                        '------- AVAILABLE TEMPLATES --------\n'.green +
                        Object.keys(supportedTemplates).join('\n') +
                        '\n------------------------------------'.green);
          }
          auth.checkCanAccess(firebase, function(err) {
            if (err) {
              console.log('PERMISSION ERROR'.red + ' - You do not have ' +
                                    'permission to use this Firebase');
              process.exit(1);
            }
            var schema = {
                  properties: {
                    template: {
                      required: true,
                      pattern: templatePattern,
                      description: 'Template:',
                      type: 'string'
                    }
                  }
                };
            prompt.get(schema, function(err, result) {
              if (err) {
                console.log('USER INPUT ERROR'.red);
                process.exit(1);
              }
              var dir = firebase;
              var projectDir = path.resolve(dir);
              if (fs.existsSync(projectDir)) {
                var i = 1;
                do {
                  dir = firebase + '_' + i++;
                  projectDir = path.resolve(dir);
                } while (fs.existsSync(projectDir));
              }
              console.log('Bootstrapping into directory \'' + dir + '\'');
              try {
                fs.mkdirSync(projectDir, '0755');
              } catch (err) {
                console.log('FILESYSTEM ERROR'.red + ' - Could not create new' +
                              ' directory');
                process.exit(1);
              }

              console.log('Downloading and unpacking template');
              var gunzip = zlib.createGunzip();
              var untar = tar.Extract({
                path: projectDir,
                strip: 1
              });
              var urlParts = url.parse(supportedTemplates[result.template].url);
              var protocol, port;
              if (urlParts.protocol === 'https:') {
                protocol = https;
                port = 443;
              } else {
                protocol = http;
                port = 80;
              }
              if (urlParts.port) {
                port = urlParts.port;
              }
              var request = protocol.get({
                host: urlParts.hostname,
                path: urlParts.pathname,
                port: port
              });
              request.on('response', function(response) {
                try {
                  response.pipe(gunzip).pipe(untar);
                } catch (err) {
                  console.log('DOWNLOAD ERROR'.red + ' - Could not download ' +
                                'template');
                  process.exit(1);
                }
                response.on('end', function() {
                  var config = path.join(
                                 projectDir,
                                 supportedTemplates[result.template].config
                               );
                  try {
                    var data = fs.readFileSync(config, 'utf8'),
                        realtimeHost = api.realtime.protocol + '//' +
                                        firebase + '.' +
                                        api.realtime.host;
                    if (api.realtime.port) {
                      realtimeHost += ':' + api.realtime.port;
                    }
                    var replaced = data.replace(
                                     supportedTemplates[result.template].configRegex,
                                     realtimeHost
                                   );
                    fs.writeFileSync(config, replaced);
                  } catch (err) {
                    console.log('INITIALIZATION ERROR'.red + ' - Couldn\'t ' +
                                  'update template with project settings');
                    process.exit(1);
                  }

                  console.log('Writing firebase.json settings file');
                  var publicPath = supportedTemplates[result.template]
                                  .settings['public'].replace(/\//g, path.sep),
                      rulesPath = supportedTemplates[result.template]
                                  .settings['rules'].replace(/\//g, path.sep);
                  var settings = {
                    'firebase': firebase,
                    'public': publicPath,
                    'rules': rulesPath
                  };
                  var settingsJSON = JSON.stringify(settings, null, 2) + "\n";
                  var settingsFile = path.join(projectDir, 'firebase.json');
                  try {
                    fs.writeFileSync(settingsFile, settingsJSON);
                  } catch (err) {
                    console.log('FILESYSTEM ERROR'.red + ' - Could not save ' +
                      'settings file');
                    process.exit(1);
                  }
                  console.log(supportedTemplates[result.template].completedMessage);
                });
              });
            });
          });
        });
      });
    });
  },
  deploy: function() {
    auth.requireLogin(function(err) {
      if (err) {
        console.log('LOGIN ERROR'.red);
        process.exit(1);
      }
      var settingsFile = path.resolve('./firebase.json');
      if (!fs.existsSync(settingsFile)) {
        console.log('INITIALIZATION ERROR'.red + ' - Directory not ' +
                      'initialized');
        process.exit(1);
      }
      try {
        var settingsJSON = fs.readFileSync(settingsFile),
            settings = JSON.parse(settingsJSON);
      } catch (err) {
        console.log('INITIALIZATION ERROR'.red +' - Could not read ' +
                          'firebase.json settings file');
        process.exit(1);
      }
      if (typeof(settings.firebase) !== 'string') {
        console.log('INITIALIZATION ERROR'.red +' - Could not read ' +
                          'firebase.json settings file');
        process.exit(1);
      }
      auth.checkCanAccess(settings.firebase, function(err, bbToken, bbSite) {
        if (err) {
          console.log('PERMISSION ERROR'.red + ' - You do not have ' +
                                'permission to use this Firebase');
          process.exit(1);
        }
        if (bbToken) {
          settings.bbToken = bbToken;
        }
        if (bbSite) {
          settings.bbSite = bbSite;
        }
        for (var i in defaultSettings) {
          if (defaultSettings.hasOwnProperty(i) &&
                !settings.hasOwnProperty(i)) {
            settings[i] = defaultSettings[i];
          }
        }
        if (path.relative('.', settings['public']).match(/^\./)) {
          console.log('PUBLIC DIRECTORY ERROR'.red + ' - public directory' +
                            ' must be within current working directory');
          process.exit(1);
        }
        if (!fs.existsSync(settings['public'])) {
          console.log('PUBLIC DIRECTORY ERROR'.red + ' - Public directory ' +
                                'does not exist');
          process.exit(1);
        }
        if (fs.readdirSync(settings['public']).length == 0) {
          console.log('PUBLIC DIRECTORY ERROR'.red + ' - Public directory ' +
                                'must not be empty');
          process.exit(1);
        }
        auth.updateRules(settings.firebase,
                         settings.rules,
                         function(statusCode, response) {
          if (response.error) {
            console.log('SECURITY RULES ERROR'.red + ' - ' +
                                response.error.replace(/\n$/, ''));
            process.exit(1);
          }
          console.log('Deploying...');
          bitballoon.deploy(settings, function(err, url) {
            if (err) {
              console.log('DEPLOY ERROR'.red + ' - Couldn\'t deploy app');
              console.log(err);
              process.exit(1);
            }
            console.log('DEPLOYED SUCCESSFULLY'.green + ' - View your app' +
                              ' at: ' + url.cyan);
            setTimeout(open, 1000, url);
          });
        });
      });
    });
  }
};
<commit_msg>adding new template for a simple chat application<commit_after>var http = require('http'),
    https = require('https'),
    fs = require('fs'),
    path = require('path'),
    zlib = require('zlib'),
    tar = require('tar'),
    url = require('url'),
    prompt = require('prompt'),
    open = require('open'),
    bitballoon = require('./bitballoon'),
    auth = require('./auth'),
    api = require('./api');

var supportedTemplates = {
  angular: {
    url: 'https://codeload.github.com/firebase/angularFire-seed/' +
              'legacy.tar.gz/master',
    settings: {
      'public': 'app',
      'rules': 'config/security-rules.json',
    },
    config: 'app/js/config.js',
    configRegex: /https:\/\/INSTANCE\.firebaseio\.com/,
    completedMessage: 'BOOTSTRAPPING SUCCESSFUL'.green + ' - Instructions and' +
                        ' setup guide here: ' +
                        'https://github.com/firebase/angularFire-seed'.cyan
  },
  chat: {
    url: 'https://codeload.github.com/firebase/chat-seed/' +
              'legacy.tar.gz/master',
    settings: {
      'public': 'public',
      'rules': 'config/rules.json',
    },
    config: 'public/index.html',
    configRegex: /https:\/\/INSTANCE\.firebaseio\.com/,
    completedMessage: 'BOOTSTRAPPING SUCCESSFUL'.green + ' - Instructions and' +
                        ' setup guide here: ' +
                        'https://github.com/firebase/chat-seed'.cyan
  }
};

var defaultSettings = {
  'public': '.'
}

module.exports = {
  init: function(argv) {
    auth.requireLogin(function(err) {
      if (err) {
        console.log('LOGIN ERROR'.red);
        process.exit(1);
      }
      auth.getFirebases(function(err, firebases) {
        if (err) {
          console.log('INITIALIZATION ERROR'.red);
          process.exit(1);
        }
        var settingsFile = path.resolve('./firebase.json');
        if (fs.existsSync(settingsFile)) {
          console.log('INITIALIZATION ERROR'.red + ' - Directory alread ' +
                        'initialized');
          process.exit(1);
        }
        if (firebases.length == 0) {
          console.log('NO FIREBASES'.red + ' - You need to create a Firebase ' +
                        'to initialize the app with at https://firebase.com');
          process.exit(1);
        }
        // Firebase names always a subset of ^[0-9a-z-]*$ so safe to regex
        var pattern = new RegExp('^(' + firebases.join('|') + ')$');
        if (!argv.firebase ||
            (typeof(argv.firebase) !== 'string') ||
            !argv.firebase.match(pattern)) {
          console.log('Choose A Firebase\n' +
                      '---------- YOUR FIREBASES ----------\n'.green +
                      firebases.join('\n') +
                      '\n------------------------------------'.green);
        }
        var schema = {
              properties: {
                firebase: {
                  required: true,
                  pattern: pattern,
                  description: 'Firebase:',
                  type: 'string'
                },
                'public': {
                  required: true,
                  description: 'Public Directory:',
                  default: defaultSettings['public'],
                  type: 'string'
                },
                rules: {
                  description: 'Security Rules File: (none)',
                  type: 'string'
                }
              }
            };
        prompt.get(schema, function(err, result) {
          if (err) {
            console.log('USER INPUT ERROR'.red);
            process.exit(1);
          }
          if (path.relative('.', result['public']).match(/^\./)) {
            console.log('PUBLIC DIRECTORY ERROR'.red + ' - Public directory ' +
                                  'must be within current working directory');
            process.exit(1);
          }
          if (!fs.existsSync(result['public'])) {
            console.log('PUBLIC DIRECTORY ERROR'.red + ' - Public directory ' +
                                  'does not exist');
            process.exit(1);
          }
          var settings = {
            firebase: result.firebase,
            'public': result['public']
          };
          if (result.rules) {
            if (fs.existsSync(result.rules) &&
                fs.statSync(result.rules).isFile()) {
              settings.rules = result.rules;
            } else {
              console.log('INITIALIZATION ERROR'.red + ' - rules file does ' +
                                    'not exist');
              process.exit(1);
            }
          }
          console.log('Initializing app into current directory');
          auth.checkCanAccess(result.firebase, function(err) {
            if (err) {
              console.log('PERMISSION ERROR'.red + ' - You do not have ' +
                                    'permission to use this Firebase');
              process.exit(1);
            }
            console.log('Writing firebase.json settings file');
            var settingsJSON = JSON.stringify(settings, null, 2) + "\n";
            try {
              fs.writeFileSync(settingsFile, settingsJSON);
              console.log('SUCCESSFULLY INITIALIZED APP'.green);
            } catch (err) {
              console.log('INITIALIZATION ERROR'.red);
              process.exit(1);
            }
          });
        });
      });
    });
  },
  bootstrap: function(argv) {
    auth.requireLogin(function(err) {
      if (err) {
        console.log('LOGIN ERROR'.red);
        process.exit(1);
      }
      auth.getFirebases(function(err, firebases) {
        if (err) {
          console.log('INITIALIZATION ERROR'.red);
          process.exit(1);
        }
        if (firebases.length == 0) {
          console.log('NO FIREBASES'.red + ' - You need to create a Firebase ' +
                        'to bootstrap the app with at https://firebase.com');
          process.exit(1);
        }
        // Firebase names always a subset of ^[0-9a-z-]*$ so safe to regex
        var firebasePattern = new RegExp('^(' + firebases.join('|') + ')$');
        var templatePattern = new RegExp('^(' +
                          Object.keys(supportedTemplates).join('|') + ')$');
        if (!argv.firebase ||
            (typeof(argv.firebase) !== 'string') ||
            !argv.firebase.match(firebasePattern)) {
          console.log('Choose A Firebase\n' +
                      '---------- YOUR FIREBASES ----------\n'.green +
                      firebases.join('\n') +
                      '\n------------------------------------'.green);
        }
        var schema = {
              properties: {
                firebase: {
                  required: true,
                  pattern: firebasePattern,
                  description: 'Firebase:',
                  type: 'string'
                }
              }
            };
        prompt.get(schema, function(err, result) {
          if (err) {
            console.log('USER INPUT ERROR'.red);
            process.exit(1);
          }
          var firebase = result.firebase;
          if (!argv.template ||
              (typeof(argv.template) !== 'string') ||
              !argv.template.match(templatePattern)) {
            console.log('Choose A Template\n' +
                        '------- AVAILABLE TEMPLATES --------\n'.green +
                        Object.keys(supportedTemplates).join('\n') +
                        '\n------------------------------------'.green);
          }
          auth.checkCanAccess(firebase, function(err) {
            if (err) {
              console.log('PERMISSION ERROR'.red + ' - You do not have ' +
                                    'permission to use this Firebase');
              process.exit(1);
            }
            var schema = {
                  properties: {
                    template: {
                      required: true,
                      pattern: templatePattern,
                      description: 'Template:',
                      type: 'string'
                    }
                  }
                };
            prompt.get(schema, function(err, result) {
              if (err) {
                console.log('USER INPUT ERROR'.red);
                process.exit(1);
              }
              var dir = firebase;
              var projectDir = path.resolve(dir);
              if (fs.existsSync(projectDir)) {
                var i = 1;
                do {
                  dir = firebase + '_' + i++;
                  projectDir = path.resolve(dir);
                } while (fs.existsSync(projectDir));
              }
              console.log('Bootstrapping into directory \'' + dir + '\'');
              try {
                fs.mkdirSync(projectDir, '0755');
              } catch (err) {
                console.log('FILESYSTEM ERROR'.red + ' - Could not create new' +
                              ' directory');
                process.exit(1);
              }

              console.log('Downloading and unpacking template');
              var gunzip = zlib.createGunzip();
              var untar = tar.Extract({
                path: projectDir,
                strip: 1
              });
              var urlParts = url.parse(supportedTemplates[result.template].url);
              var protocol, port;
              if (urlParts.protocol === 'https:') {
                protocol = https;
                port = 443;
              } else {
                protocol = http;
                port = 80;
              }
              if (urlParts.port) {
                port = urlParts.port;
              }
              var request = protocol.get({
                host: urlParts.hostname,
                path: urlParts.pathname,
                port: port
              });
              request.on('response', function(response) {
                try {
                  response.pipe(gunzip).pipe(untar);
                } catch (err) {
                  console.log('DOWNLOAD ERROR'.red + ' - Could not download ' +
                                'template');
                  process.exit(1);
                }
                untar.on('end', function() {
                  var config = path.join(
                                 projectDir,
                                 supportedTemplates[result.template].config
                               );
                  try {
                    var data = fs.readFileSync(config, 'utf8'),
                        realtimeHost = api.realtime.protocol + '//' +
                                        firebase + '.' +
                                        api.realtime.host;
                    if (api.realtime.port) {
                      realtimeHost += ':' + api.realtime.port;
                    }
                    var replaced = data.replace(
                                     supportedTemplates[result.template].configRegex,
                                     realtimeHost
                                   );
                    fs.writeFileSync(config, replaced);
                  } catch (err) {
                    console.log('INITIALIZATION ERROR'.red + ' - Couldn\'t ' +
                                  'update template with project settings');
                    process.exit(1);
                  }

                  console.log('Writing firebase.json settings file');
                  var publicPath = supportedTemplates[result.template]
                                  .settings['public'].replace(/\//g, path.sep),
                      rulesPath = supportedTemplates[result.template]
                                  .settings['rules'].replace(/\//g, path.sep);
                  var settings = {
                    'firebase': firebase,
                    'public': publicPath,
                    'rules': rulesPath
                  };
                  var settingsJSON = JSON.stringify(settings, null, 2) + "\n";
                  var settingsFile = path.join(projectDir, 'firebase.json');
                  try {
                    fs.writeFileSync(settingsFile, settingsJSON);
                  } catch (err) {
                    console.log('FILESYSTEM ERROR'.red + ' - Could not save ' +
                      'settings file');
                    process.exit(1);
                  }
                  console.log(supportedTemplates[result.template].completedMessage);
                });
              });
            });
          });
        });
      });
    });
  },
  deploy: function() {
    auth.requireLogin(function(err) {
      if (err) {
        console.log('LOGIN ERROR'.red);
        process.exit(1);
      }
      var settingsFile = path.resolve('./firebase.json');
      if (!fs.existsSync(settingsFile)) {
        console.log('INITIALIZATION ERROR'.red + ' - Directory not ' +
                      'initialized');
        process.exit(1);
      }
      try {
        var settingsJSON = fs.readFileSync(settingsFile),
            settings = JSON.parse(settingsJSON);
      } catch (err) {
        console.log('INITIALIZATION ERROR'.red +' - Could not read ' +
                          'firebase.json settings file');
        process.exit(1);
      }
      if (typeof(settings.firebase) !== 'string') {
        console.log('INITIALIZATION ERROR'.red +' - Could not read ' +
                          'firebase.json settings file');
        process.exit(1);
      }
      auth.checkCanAccess(settings.firebase, function(err, bbToken, bbSite) {
        if (err) {
          console.log('PERMISSION ERROR'.red + ' - You do not have ' +
                                'permission to use this Firebase');
          process.exit(1);
        }
        if (bbToken) {
          settings.bbToken = bbToken;
        }
        if (bbSite) {
          settings.bbSite = bbSite;
        }
        for (var i in defaultSettings) {
          if (defaultSettings.hasOwnProperty(i) &&
                !settings.hasOwnProperty(i)) {
            settings[i] = defaultSettings[i];
          }
        }
        if (path.relative('.', settings['public']).match(/^\./)) {
          console.log('PUBLIC DIRECTORY ERROR'.red + ' - public directory' +
                            ' must be within current working directory');
          process.exit(1);
        }
        if (!fs.existsSync(settings['public'])) {
          console.log('PUBLIC DIRECTORY ERROR'.red + ' - Public directory ' +
                                'does not exist');
          process.exit(1);
        }
        if (fs.readdirSync(settings['public']).length == 0) {
          console.log('PUBLIC DIRECTORY ERROR'.red + ' - Public directory ' +
                                'must not be empty');
          process.exit(1);
        }
        auth.updateRules(settings.firebase,
                         settings.rules,
                         function(statusCode, response) {
          if (response.error) {
            console.log('SECURITY RULES ERROR'.red + ' - ' +
                                response.error.replace(/\n$/, ''));
            process.exit(1);
          }
          console.log('Deploying...');
          bitballoon.deploy(settings, function(err, url) {
            if (err) {
              console.log('DEPLOY ERROR'.red + ' - Couldn\'t deploy app');
              console.log(err);
              process.exit(1);
            }
            console.log('DEPLOYED SUCCESSFULLY'.green + ' - View your app' +
                              ' at: ' + url.cyan);
            setTimeout(open, 1000, url);
          });
        });
      });
    });
  }
};
<|endoftext|>


<commit_before>var _ = require('underscore'),
    util = require('util'),
    i18n = require('i18n-abide'),
    path = require('path'),
    expressValidator = require('express-validator'),
    moment = require('moment'),
    flash = require('connect-flash'),
    cookieParser = require('cookie-parser'),
    bodyParser = require('body-parser'),
    methodOverride = require('method-override'),
    session = require('express-session'),
    logger = require('morgan'),
    errorHandler = require('errorhandler'),

    db_name = process.env.DB_NAME || 'test',
    config = {
        sessionSecret: 'localhost',
        cookieSecret: 'hostlocal',
        uri: 'http://localhost:3000/',
        port: process.env.PORT || 3000,
        db_name: db_name,
        auth: {},
        mongo: {
            servers: ['mongodb://localhost/' + db_name],
            replset: null
        },
        redis: {},
        locales: ['en'],
        defaultLocale: 'en',
        translation_directory: path.join(__dirname, '..', '..', '..', 'public', 'js'),
        trust_proxy: false,
        session_resave: false,
        session_saveUninitialized: false,
        session_rolling: false,
        session_ttl: 86400000,
        session_name: 'connect.sid'
    };

module.exports = function(express, settings, db){
    var app = express();
    var RedisStore,
        MongoStore;

    settings = settings || {};
    config = _.extend(config, settings);

    app.conf = config;

    var sessionOpts = {
        secret: app.conf.sessionSecret,
        name: app.conf.session_name,
        resave: app.conf.session_resave,
        saveUninitialized: app.conf.session_saveUninitialized,
        rolling: app.conf.session_rolling,
        cookie: {
            maxAge: app.conf.session_ttl
        }
    };

    if (settings.trust_proxy){
        app.enable('trust proxy');
    }

    app.use(cookieParser(app.conf.cookieSecret));

    // ## Environment specific configuration
    if (app.settings.env === 'test'){
        app.use(errorHandler({
            dumpExceptions: true,
            showStack: true
        }));

        app.use(session({secret:'testing-secret', resave: config.session_resave, saveUninitialized: config.session_saveUninitialized}));
    }
    else {
        if (app.settings.env === 'development'){
            if (settings.useBunyan !== true){
                app.use(logger('dev'));

                app.use(errorHandler({
                    dumpExceptions: true,
                    showStack: true
                }));
            }
        }
        else {
            if (settings.useBunyan !== true){
                app.use(logger('combined'));
                app.use(errorHandler());
            }
        }

        if (app.conf.redis && app.conf.redis.host !== undefined && app.conf.redis.port !== undefined){
            console.info('[libby] sessions in redis %s:%s', app.conf.redis.host, app.conf.redis.port);
            RedisStore = require('connect-redis')(session);
            var storeOpts = {
                host: app.conf.redis.host,
                port: app.conf.redis.port,
                ttl: app.conf.session_ttl / 1000
            };
            if (app.conf.redis.password) {
                storeOpts.pass = app.conf.redis.password;
            }
            sessionOpts.store = new RedisStore(storeOpts);
        }
        else if (config.mongo && config.mongo.servers && db){
            console.info('[libby] sessions in mongo %s repl: %s', app.conf.mongo.servers.join(', '), app.conf.mongo.replset);
            MongoStore = require('./mongostore')(session);
            sessionOpts.store = new MongoStore(db.connection.db, {
                collectionName: 'sessions',
                ttl: app.conf.session_ttl
            });
        }
        else {
            console.info('[libby] sessions in memory.');
        }

        app.use(session(sessionOpts));
    }

    // Parses x-www-form-urlencoded request bodies (and json)
    app.use(bodyParser.urlencoded({extended: true}));
    app.use(bodyParser.json());
    app.use(methodOverride());

    app.use(expressValidator({
        errorFormatter: function(param, msg, value) {
            var namespace = param.split('.'),
                root = namespace.shift(),
                formParam = root;

            while(namespace.length) {
                formParam += '[' + namespace.shift() + ']';
            }
            return {
                param: formParam,
                msg: msg,
                value: value
            };
        }
    }));

    // Translation settings
    console.info('[libby] i18n-abide default: %s -- supported: %s', app.conf.defaultLocale, app.conf.locales.join(', '));
    console.info('[libby] i18n-abide translation directory: %s', app.conf.translation_directory);
    app.use(i18n.abide({
        supported_languages: app.conf.locales,
        default_lang: app.conf.defaultLocale,
        translation_directory: app.conf.translation_directory
    }));

    // Middleware setting __ translation shortcut
    app.use(function(req, res, next){
        res.locals.__ = function(){
            var args = arguments;
            if (args.length === 0){ return ""; }
            args[0] = res.locals.gettext(args[0]);
            return util.format.apply(this, args);
        };
        next();
    });

    // Messages across redirects - for some reason this didn't work properly when placed higher in this file
    app.use(flash());
    app.use(function(req, res, next){
        if (req.session === undefined){
            console.error('[libby] ERROR: no flash(). No session available.');
        }
        else {
            res.locals.messages = req.flash();
        }
        next();
    });

    return app;
};
<commit_msg>Added session config to app object<commit_after>var _ = require('underscore'),
    util = require('util'),
    i18n = require('i18n-abide'),
    path = require('path'),
    expressValidator = require('express-validator'),
    moment = require('moment'),
    flash = require('connect-flash'),
    cookieParser = require('cookie-parser'),
    bodyParser = require('body-parser'),
    methodOverride = require('method-override'),
    session = require('express-session'),
    logger = require('morgan'),
    errorHandler = require('errorhandler'),

    db_name = process.env.DB_NAME || 'test',
    config = {
        sessionSecret: 'localhost',
        cookieSecret: 'hostlocal',
        uri: 'http://localhost:3000/',
        port: process.env.PORT || 3000,
        db_name: db_name,
        auth: {},
        mongo: {
            servers: ['mongodb://localhost/' + db_name],
            replset: null
        },
        redis: {},
        locales: ['en'],
        defaultLocale: 'en',
        translation_directory: path.join(__dirname, '..', '..', '..', 'public', 'js'),
        trust_proxy: false,
        session_resave: false,
        session_saveUninitialized: false,
        session_rolling: false,
        session_ttl: 86400000,
        session_name: 'connect.sid'
    };

module.exports = function(express, settings, db){
    var app = express();
    var RedisStore,
        MongoStore;

    settings = settings || {};
    config = _.extend(config, settings);

    app.conf = config;

    var sessionOpts = {
        secret: app.conf.sessionSecret,
        name: app.conf.session_name,
        resave: app.conf.session_resave,
        saveUninitialized: app.conf.session_saveUninitialized,
        rolling: app.conf.session_rolling,
        cookie: {
            maxAge: app.conf.session_ttl
        }
    };

    if (settings.trust_proxy){
        app.enable('trust proxy');
    }

    app.use(cookieParser(app.conf.cookieSecret));

    // ## Environment specific configuration
    if (app.settings.env === 'test'){
        app.use(errorHandler({
            dumpExceptions: true,
            showStack: true
        }));

        app.use(session({secret:'testing-secret', resave: config.session_resave, saveUninitialized: config.session_saveUninitialized}));
    }
    else {
        if (app.settings.env === 'development'){
            if (settings.useBunyan !== true){
                app.use(logger('dev'));

                app.use(errorHandler({
                    dumpExceptions: true,
                    showStack: true
                }));
            }
        }
        else {
            if (settings.useBunyan !== true){
                app.use(logger('combined'));
                app.use(errorHandler());
            }
        }

        if (app.conf.redis && app.conf.redis.host !== undefined && app.conf.redis.port !== undefined){
            console.info('[libby] sessions in redis %s:%s', app.conf.redis.host, app.conf.redis.port);
            RedisStore = require('connect-redis')(session);
            var storeOpts = {
                host: app.conf.redis.host,
                port: app.conf.redis.port,
                ttl: app.conf.session_ttl / 1000
            };
            if (app.conf.redis.password) {
                storeOpts.pass = app.conf.redis.password;
            }
            sessionOpts.store = new RedisStore(storeOpts);
        }
        else if (config.mongo && config.mongo.servers && db){
            console.info('[libby] sessions in mongo %s repl: %s', app.conf.mongo.servers.join(', '), app.conf.mongo.replset);
            MongoStore = require('./mongostore')(session);
            sessionOpts.store = new MongoStore(db.connection.db, {
                collectionName: 'sessions',
                ttl: app.conf.session_ttl
            });
        }
        else {
            console.info('[libby] sessions in memory.');
        }

        app.use(session(sessionOpts));
    }

    // Parses x-www-form-urlencoded request bodies (and json)
    app.use(bodyParser.urlencoded({extended: true}));
    app.use(bodyParser.json());
    app.use(methodOverride());

    app.use(expressValidator({
        errorFormatter: function(param, msg, value) {
            var namespace = param.split('.'),
                root = namespace.shift(),
                formParam = root;

            while(namespace.length) {
                formParam += '[' + namespace.shift() + ']';
            }
            return {
                param: formParam,
                msg: msg,
                value: value
            };
        }
    }));

    // Translation settings
    console.info('[libby] i18n-abide default: %s -- supported: %s', app.conf.defaultLocale, app.conf.locales.join(', '));
    console.info('[libby] i18n-abide translation directory: %s', app.conf.translation_directory);
    app.use(i18n.abide({
        supported_languages: app.conf.locales,
        default_lang: app.conf.defaultLocale,
        translation_directory: app.conf.translation_directory
    }));

    // Middleware setting __ translation shortcut
    app.use(function(req, res, next){
        res.locals.__ = function(){
            var args = arguments;
            if (args.length === 0){ return ""; }
            args[0] = res.locals.gettext(args[0]);
            return util.format.apply(this, args);
        };
        next();
    });

    // Messages across redirects - for some reason this didn't work properly when placed higher in this file
    app.use(flash());
    app.use(function(req, res, next){
        if (req.session === undefined){
            console.error('[libby] ERROR: no flash(). No session available.');
        }
        else {
            res.locals.messages = req.flash();
        }
        next();
    });

    // add useful session config to app
    app.sessionConfig = {
        cookieParser: cookieParser,
        key: sessionOpts.name,
        secret: sessionOpts.secret,
        store: sessionOpts.store
    };

    return app;
};
<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const Bluebird = require('bluebird');
const request = Bluebird.promisifyAll(require('request'));
const knex = require('knex');

const app = express();
const PAGE_TOKEN = process.env['PAGE_TOKEN'];

const flows = {
  authorisation: require('./flows/authorisation')
};
const authy = flows.authorisation.authy;
const wit = require('./wit');

// Set up database connection.
if (!_.has(process.env, 'DATABASE_URL')) {
  console.log('DATABASE_URL env variable missing');
  process.exit(-1);
}
var knex = require('knex')({
  client: 'postgres',
  connection: process.env.DATABASE_URL
});

// view engine setup
// app.set('views', path.join(__dirname, '../views'));
// app.set('view engine', 'html');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, '../public')));

// Routing

app.get('/', function (req, res, next) {
  knex('Users')
  .then(users => {
    console.log(users);
    res.json('dawg');
  });
});

app.get('/webhook/', function (req, res) {
  if (req.query['hub.verify_token'] === 'my_body_is_my_temple') {
    res.send(req.query['hub.challenge']);
  }
  res.status(400);
  res.json({ error: 'Error, wrong validation token' });
});

app.post('/webhook/', function (req, res) {
  let messaging_events = req.body.entry[0].messaging;
  _.forEach(messaging_events, (event) => {
    let sender = event.sender;
    runStateMachine(sender, event.message.text)
    .then((reply) => {
      sendFacebookMessage(sender.id, reply);
    });
  });
  res.status(200).send();
});

function runStateMachine(user, message) {
  let handler = db[user.id] || loggedOutState;
  return handler(message, user)
  .then((result) => {
    db[user.id] = result[0];
    return result[1];
  }).catch((err) => {
    console.error(err);
  });
}

// pragma mark - states

function loggedOutState (input, user) {
  return flows.authorisation.start(loggedInState).then((nextState) => {
    return [
      nextState,
      `Hi ${user.id}, Thanks for getting in touch!\n
To continue, I'm going to have to verify that you are.
Please type in the token I just sent you!`
    ];
  });
}

function loggedInState (input, user) {
  return Bluebird.resolve([
    loggedInState,
    input
  ]);
}

// pragma mark - Functions

function registerUser (email, phone) {
  return authy.register_userAsync(email, phone, '354', false)
  .then((res) => {
    return res.user.id;
  })
  .catch((err) => {
    console.error('error communicating with authy');
    console.error(err);
  });
}

function sendFacebookMessage(sender, text) {
  let messageData = { text };
  return request.postAsync({
    url: 'https://graph.facebook.com/v2.6/me/messages',
    qs: { access_token: PAGE_TOKEN },
    json: {
      recipient: { id: sender },
      message: messageData,
    }
  }).then(function (response) {
    if (response.body.error) {
      console.log('Error: ', response.body.error);
    }
  }).catch(function (err) {
    console.error(err);
  });
}

// Setup the development environment.
if (app.get('env') === 'development') {
  // Error handling.
  app.use(function(err, req, res, next) {
    console.error({ err: err }, 'Error hit in development.');
    res.status(err.status || 500);
    res.json({
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  console.error({ err: err }, 'Error hit in development.');
  res.status(err.status || 500);
  res.json({
    message: err.message,
    error: {}
  });
});

app.set('port', process.env.PORT || 3000);

module.exports = app;
<commit_msg>Fix broken build<commit_after>'use strict';

const _ = require('lodash');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const Bluebird = require('bluebird');
const request = Bluebird.promisifyAll(require('request'));

const app = express();
const PAGE_TOKEN = process.env['PAGE_TOKEN'];

const flows = {
  authorisation: require('./flows/authorisation')
};
const authy = flows.authorisation.authy;
const wit = require('./wit');

// Set up database connection.
if (!_.has(process.env, 'DATABASE_URL')) {
  console.log('DATABASE_URL env variable missing');
  process.exit(-1);
}
var knex = require('knex')({
  client: 'postgres',
  connection: process.env.DATABASE_URL
});

// view engine setup
// app.set('views', path.join(__dirname, '../views'));
// app.set('view engine', 'html');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, '../public')));

// Routing

app.get('/', function (req, res, next) {
  knex('Users')
  .then(users => {
    console.log(users);
    res.json('dawg');
  });
});

app.get('/webhook/', function (req, res) {
  if (req.query['hub.verify_token'] === 'my_body_is_my_temple') {
    res.send(req.query['hub.challenge']);
  }
  res.status(400);
  res.json({ error: 'Error, wrong validation token' });
});

app.post('/webhook/', function (req, res) {
  let messaging_events = req.body.entry[0].messaging;
  _.forEach(messaging_events, (event) => {
    let sender = event.sender;
    runStateMachine(sender, event.message.text)
    .then((reply) => {
      sendFacebookMessage(sender.id, reply);
    });
  });
  res.status(200).send();
});

function runStateMachine(user, message) {
  let handler = db[user.id] || loggedOutState;
  return handler(message, user)
  .then((result) => {
    db[user.id] = result[0];
    return result[1];
  }).catch((err) => {
    console.error(err);
  });
}

// pragma mark - states

function loggedOutState (input, user) {
  return flows.authorisation.start(loggedInState).then((nextState) => {
    return [
      nextState,
      `Hi ${user.id}, Thanks for getting in touch!\n
To continue, I'm going to have to verify that you are.
Please type in the token I just sent you!`
    ];
  });
}

function loggedInState (input, user) {
  return Bluebird.resolve([
    loggedInState,
    input
  ]);
}

// pragma mark - Functions

function registerUser (email, phone) {
  return authy.register_userAsync(email, phone, '354', false)
  .then((res) => {
    return res.user.id;
  })
  .catch((err) => {
    console.error('error communicating with authy');
    console.error(err);
  });
}

function sendFacebookMessage(sender, text) {
  let messageData = { text };
  return request.postAsync({
    url: 'https://graph.facebook.com/v2.6/me/messages',
    qs: { access_token: PAGE_TOKEN },
    json: {
      recipient: { id: sender },
      message: messageData,
    }
  }).then(function (response) {
    if (response.body.error) {
      console.log('Error: ', response.body.error);
    }
  }).catch(function (err) {
    console.error(err);
  });
}

// Setup the development environment.
if (app.get('env') === 'development') {
  // Error handling.
  app.use(function(err, req, res, next) {
    console.error({ err: err }, 'Error hit in development.');
    res.status(err.status || 500);
    res.json({
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  console.error({ err: err }, 'Error hit in development.');
  res.status(err.status || 500);
  res.json({
    message: err.message,
    error: {}
  });
});

app.set('port', process.env.PORT || 3000);

module.exports = app;
<|endoftext|>