keep replace replace replace replace keep keep keep keep keep

var runtimeCfgString = _.omit(__meteor_runtime_config__,

////////// Requires //////////

var fs = Npm.require("fs");
var http = Npm.require("http");
var os = Npm.require("os");
var path = Npm.require("path");
var url = Npm.require("url");
var crypto = Npm.require("crypto");

var connect = Npm.require('connect');
var useragent = Npm.require('useragent');
var send = Npm.require('send');

var Future = Npm.require('fibers/future');
var Fiber = Npm.require('fibers');

var SHORT_SOCKET_TIMEOUT = 5*1000;
var LONG_SOCKET_TIMEOUT = 120*1000;

WebApp = {};
WebAppInternals = {};

WebApp.defaultArch = 'web.browser';

// XXX maps archs to manifests
WebApp.clientPrograms = {};

// XXX maps archs to program path on filesystem
var archPath = {};

var bundledJsCssPrefix;

// Keepalives so that when the outer server dies unceremoniously and
// doesn't kill us, we quit ourselves. A little gross, but better than
// pidfiles.
// XXX This should really be part of the boot script, not the webapp package.
//     Or we should just get rid of it, and rely on containerization.

var initKeepalive = function () {
  var keepaliveCount = 0;

  process.stdin.on('data', function (data) {
    keepaliveCount = 0;
  });

  process.stdin.resume();

  setInterval(function () {
    keepaliveCount ++;
    if (keepaliveCount >= 3) {
      console.log("Failed to receive keepalive! Exiting.");
      process.exit(1);
    }
  }, 3000);
};

var sha1 = function (contents) {
  var hash = crypto.createHash('sha1');
  hash.update(contents);
  return hash.digest('hex');
};

var readUtf8FileSync = function (filename) {
  return Future.wrap(fs.readFile)(filename, 'utf8').wait();
};

// #BrowserIdentification
//
// We have multiple places that want to identify the browser: the
// unsupported browser page, the appcache package, and, eventually
// delivering browser polyfills only as needed.
//
// To avoid detecting the browser in multiple places ad-hoc, we create a
// Meteor "browser" object. It uses but does not expose the npm
// useragent module (we could choose a different mechanism to identify
// the browser in the future if we wanted to).  The browser object
// contains
//
// * `name`: the name of the browser in camel case
// * `major`, `minor`, `patch`: integers describing the browser version
//
// Also here is an early version of a Meteor `request` object, intended
// to be a high-level description of the request without exposing
// details of connect's low-level `req`.  Currently it contains:
//
// * `browser`: browser identification object described above
// * `url`: parsed url, including parsed query params
//
// As a temporary hack there is a `categorizeRequest` function on WebApp which
// converts a connect `req` to a Meteor `request`. This can go away once smart
// packages such as appcache are being passed a `request` object directly when
// they serve content.
//
// This allows `request` to be used uniformly: it is passed to the html
// attributes hook, and the appcache package can use it when deciding
// whether to generate a 404 for the manifest.
//
// Real routing / server side rendering will probably refactor this
// heavily.

// e.g. "Mobile Safari" => "mobileSafari"
var camelCase = function (name) {
  var parts = name.split(' ');
  parts[0] = parts[0].toLowerCase();
  for (var i = 1;  i < parts.length;  ++i) {
    parts[i] = parts[i].charAt(0).toUpperCase() + parts[i].substr(1);
  }
  return parts.join('');
};

var identifyBrowser = function (userAgentString) {
  var userAgent = useragent.lookup(userAgentString);
  return {
    name: camelCase(userAgent.family),
    major: +userAgent.major,
    minor: +userAgent.minor,
    patch: +userAgent.patch
  };
};

// XXX Refactor as part of implementing real routing.
WebAppInternals.identifyBrowser = identifyBrowser;

WebApp.categorizeRequest = function (req) {
  return {
    browser: identifyBrowser(req.headers['user-agent']),
    url: url.parse(req.url, true)
  };
};

// HTML attribute hooks: functions to be called to determine any attributes to
// be added to the '<html>' tag. Each function is passed a 'request' object (see
// #BrowserIdentification) and should return a string,
var htmlAttributeHooks = [];
var getHtmlAttributes = function (request) {
  var combinedAttributes  = {};
  _.each(htmlAttributeHooks || [], function (hook) {
    var attributes = hook(request);
    if (attributes === null)
      return;
    if (typeof attributes !== 'object')
      throw Error("HTML attribute hook must return null or object");
    _.extend(combinedAttributes, attributes);
  });
  return combinedAttributes;
};
WebApp.addHtmlAttributeHook = function (hook) {
  htmlAttributeHooks.push(hook);
};

// Serve app HTML for this URL?
var appUrl = function (url) {
  if (url === '/favicon.ico' || url === '/robots.txt')
    return false;

  // NOTE: app.manifest is not a web standard like favicon.ico and
  // robots.txt. It is a file name we have chosen to use for HTML5
  // appcache URLs. It is included here to prevent using an appcache
  // then removing it from poisoning an app permanently. Eventually,
  // once we have server side routing, this won't be needed as
  // unknown URLs with return a 404 automatically.
  if (url === '/app.manifest')
    return false;

  // Avoid serving app HTML for declared routes such as /sockjs/.
  if (RoutePolicy.classify(url))
    return false;

  // we currently return app HTML on all URLs by default
  return true;
};

// Calculate a hash of all the client resources downloaded by the
// browser, including the application HTML, runtime config, code, and
// static files.
//
// This hash *must* change if any resources seen by the browser
// change, and ideally *doesn't* change for any server-only changes
// (but the second is a performance enhancement, not a hard
// requirement).

var calculateClientHash = function (manifest, includeFilter) {
  var hash = crypto.createHash('sha1');
  // Omit the old hashed client values in the new hash. These may be
  // modified in the new boilerplate.
  hash.update(JSON.stringify(_.omit(__meteor_runtime_config__,
               ['autoupdateVersion', 'autoupdateVersionRefreshable']), 'utf8'));
  _.each(manifest, function (resource) {
      if ((! includeFilter || includeFilter(resource.type)) &&
          (resource.where === 'client' || resource.where === 'internal')) {
      hash.update(resource.path);
      hash.update(resource.hash);
    }
  });
  return hash.digest('hex');
};

// We need to calculate the client hash after all packages have loaded
// to give them a chance to populate __meteor_runtime_config__.
//
// Calculating the hash during startup means that packages can only
// populate __meteor_runtime_config__ during load, not during startup.
//
// Calculating instead it at the beginning of main after all startup
// hooks had run would allow packages to also populate
// __meteor_runtime_config__ during startup, but that's too late for
// autoupdate because it needs to have the client hash at startup to
// insert the auto update version itself into
// __meteor_runtime_config__ to get it to the client.
//
// An alternative would be to give autoupdate a "post-start,
// pre-listen" hook to allow it to insert the auto update version at
// the right moment.

Meteor.startup(function () {
  WebApp.clientHash = function (archName) {
    archName = archName || WebApp.defaultArch;
    return calculateClientHash(WebApp.clientPrograms[archName].manifest);
  };

  WebApp.calculateClientHashRefreshable = function (archName) {
    archName = archName || WebApp.defaultArch;
    return calculateClientHash(WebApp.clientPrograms[archName].manifest,
      function (name) {
        return name === "css";
      });
  };
  WebApp.calculateClientHashNonRefreshable = function (archName) {
    archName = archName || WebApp.defaultArch;
    return calculateClientHash(WebApp.clientPrograms[archName].manifest,
      function (name) {
        return name !== "css";
      });
  };
});

// When we have a request pending, we want the socket timeout to be long, to
// give ourselves a while to serve it, and to allow sockjs long polls to
// complete.  On the other hand, we want to close idle sockets relatively
// quickly, so that we can shut down relatively promptly but cleanly, without
// cutting off anyone's response.
WebApp._timeoutAdjustmentRequestCallback = function (req, res) {
  // this is really just req.socket.setTimeout(LONG_SOCKET_TIMEOUT);
  req.setTimeout(LONG_SOCKET_TIMEOUT);
  // Insert our new finish listener to run BEFORE the existing one which removes
  // the response from the socket.
  var finishListeners = res.listeners('finish');
  // XXX Apparently in Node 0.12 this event is now called 'prefinish'.
  // https://github.com/joyent/node/commit/7c9b6070
  res.removeAllListeners('finish');
  res.on('finish', function () {
    res.setTimeout(SHORT_SOCKET_TIMEOUT);
  });
  _.each(finishListeners, function (l) { res.on('finish', l); });
};

// Will be updated by main before we listen.
// Map from client arch to boilerplate object.
// Boilerplate object has:
//   - func: XXX
//   - baseData: XXX
var boilerplateByArch = {};

// Given a request (as returned from `categorizeRequest`), return the
// boilerplate HTML to serve for that request. Memoizes on HTML
// attributes (used by, eg, appcache) and whether inline scripts are
// currently allowed.
var getBoilerplate = _.memoize(function (request, arch) {
  return boilerplateByArch[arch].toHTML();
}, function (request) {
  var htmlAttributes = getHtmlAttributes(request);
  // The only thing that changes from request to request (for now) are
  // the HTML attributes (used by, eg, appcache) and whether inline
  // scripts are allowed, so we can memoize based on that.
  return JSON.stringify({
    inlineScriptsAllowed: inlineScriptsAllowed,
    htmlAttributes: htmlAttributes
  });
});

var generateBoilerplateInstance = function (arch, manifest, additionalOptions) {
  additionalOptions = additionalOptions || {};
  var runtimeConfig = _.defaults(__meteor_runtime_config__,
    additionalOptions.runtimeConfigDefaults || {}
  );

  return new Boilerplate(arch, manifest,
    _.extend({
      urlMapper:
        function (url) { return getUrlPrefixForArch(arch) + url; },
      pathMapper: function (itemPath) {
        return path.join(archPath[arch], itemPath); },
      baseDataExtension: {
        additionalStaticJs: _.map(
          additionalStaticJs || [],
          function (contents, pathname) {
            return {
              pathname: pathname,
              contents: contents
            };
          }
        ),
        meteorRuntimeConfig: JSON.stringify(runtimeConfig),
        rootUrlPathPrefix: __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || '',
        bundledJsCssPrefix: bundledJsCssPrefix ||
          __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || '',
        inlineScriptsAllowed: WebAppInternals.inlineScriptsAllowed(),
        inline: additionalOptions.inline
      }
    }, additionalOptions)
  );
};

// Serve static files from the manifest or added with
// `addStaticJs`. Exported for tests.
WebAppInternals.staticFilesMiddleware = function (staticFiles, req, res, next) {
  if ('GET' != req.method && 'HEAD' != req.method) {
    next();
    return;
  }
  var pathname = connect.utils.parseUrl(req).pathname;
  try {
    pathname = decodeURIComponent(pathname);
  } catch (e) {
    next();
    return;
  }

  // XXX think of a better name
  if (pathname === "/cordova/manifest.json") {
    var cordovaArch = "web.cordova";
    var program = WebApp.clientPrograms[cordovaArch];
    res.writeHead(200, {
      'Content-type': 'application/json; charset=UTF-8',
      'Access-Control-Allow-Origin': '*'
    });
    // Only send the inlineManifest
    res.write(JSON.stringify(WebApp.clientPrograms[cordovaArch]));
    res.end();
    return;
  }

  var serveStaticJs = function (s) {
    res.writeHead(200, {
      'Content-type': 'application/javascript; charset=UTF-8'
    });
    res.write(s);
    res.end();
  };

  if (pathname === "/meteor_runtime_config.js" &&
      ! WebAppInternals.inlineScriptsAllowed()) {
    serveStaticJs("__meteor_runtime_config__ = " +
                  JSON.stringify(__meteor_runtime_config__) + ";");
    return;
  } else if (_.has(additionalStaticJs, pathname) &&
              ! WebAppInternals.inlineScriptsAllowed()) {
    serveStaticJs(additionalStaticJs[pathname]);
    return;
  }

  if (!_.has(staticFiles, pathname)) {
    next();
    return;
  }

  // We don't need to call pause because, unlike 'static', once we call into
  // 'send' and yield to the event loop, we never call another handler with
  // 'next'.

  var info = staticFiles[pathname];

  // Cacheable files are files that should never change. Typically
  // named by their hash (eg meteor bundled js and css files).
  // We cache them ~forever (1yr).
  //
  // We cache non-cacheable files anyway. This isn't really correct, as users
  // can change the files and changes won't propagate immediately. However, if
  // we don't cache them, browsers will 'flicker' when rerendering
  // images. Eventually we will probably want to rewrite URLs of static assets
  // to include a query parameter to bust caches. That way we can both get
  // good caching behavior and allow users to change assets without delay.
  // https://github.com/meteor/meteor/issues/773
  var maxAge = info.cacheable
        ? 1000 * 60 * 60 * 24 * 365
        : 1000 * 60 * 60 * 24;

  // Set the X-SourceMap header, which current Chrome understands.
  // (The files also contain '//#' comments which FF 24 understands and
  // Chrome doesn't understand yet.)
  //
  // Eventually we should set the SourceMap header but the current version of
  // Chrome and no version of FF supports it.
  //
  // To figure out if your version of Chrome should support the SourceMap
  // header,
  //   - go to chrome://version. Let's say the Chrome version is
  //      28.0.1500.71 and the Blink version is 537.36 (@153022)
  //   - go to http://src.chromium.org/viewvc/blink/branches/chromium/1500/Source/core/inspector/InspectorPageAgent.cpp?view=log
  //     where the "1500" is the third part of your Chrome version
  //   - find the first revision that is no greater than the "153022"
  //     number.  That's probably the first one and it probably has
  //     a message of the form "Branch 1500 - blink@r149738"
  //   - If *that* revision number (149738) is at least 151755,
  //     then Chrome should support SourceMap (not just X-SourceMap)
  // (The change is https://codereview.chromium.org/15832007)
  //
  // You also need to enable source maps in Chrome: open dev tools, click
  // the gear in the bottom right corner, and select "enable source maps".
  //
  // Firefox 23+ supports source maps but doesn't support either header yet,
  // so we include the '//#' comment for it:
  //   https://bugzilla.mozilla.org/show_bug.cgi?id=765993
  // In FF 23 you need to turn on `devtools.debugger.source-maps-enabled`
  // in `about:config` (it is on by default in FF 24).
  if (info.sourceMapUrl)
    res.setHeader('X-SourceMap', info.sourceMapUrl);

  if (info.type === "js") {
    res.setHeader("Content-Type", "application/javascript; charset=UTF-8");
  } else if (info.type === "css") {
    res.setHeader("Content-Type", "text/css; charset=UTF-8");
  }

  send(req, info.absolutePath)
    .maxage(maxAge)
    .hidden(true)  // if we specified a dotfile in the manifest, serve it
    .on('error', function (err) {
      Log.error("Error serving static file " + err);
      res.writeHead(500);
      res.end();
    })
    .on('directory', function () {
      Log.error("Unexpected directory " + info.absolutePath);
      res.writeHead(500);
      res.end();
    })
    .pipe(res);
};

var getUrlPrefixForArch = function (arch) {
  // XXX we rely on the fact that arch names don't contain slashes
  // in that case we would need to uri escape it
  return arch === WebApp.defaultArch ? '' : '/' + arch.replace(/^web\./, '');
};

var runWebAppServer = function () {
  var shuttingDown = false;
  var syncQueue = new Meteor._SynchronousQueue();

  var getItemPathname = function (itemUrl) {
    return decodeURIComponent(url.parse(itemUrl).pathname);
  };

  var staticFiles;

  WebAppInternals.reloadClientPrograms = function () {
    syncQueue.runTask(function() {
      staticFiles = {};
      var getClientManifest = function (clientPath, arch) {
        // read the control for the client we'll be serving up
        clientJsonPath = path.join(__meteor_bootstrap__.serverDir,
                                   clientPath);
        clientDir = path.dirname(clientJsonPath);
        clientJson = JSON.parse(readUtf8FileSync(clientJsonPath));
        if (clientJson.format !== "web-program-pre1")
          throw new Error("Unsupported format for client assets: " +
                          JSON.stringify(clientJson.format));

        var urlPrefix = getUrlPrefixForArch(arch);

        _.each(clientJson.manifest, function (item) {
          if (item.url && item.where === "client") {
            staticFiles[urlPrefix + getItemPathname(item.url)] = {
              path: item.path,
              absolutePath: path.join(clientDir, item.path),
              cacheable: item.cacheable,
              // Link from source to its map
              sourceMapUrl: item.sourceMapUrl,
              type: item.type
            };

            if (item.sourceMap) {
              // Serve the source map too, under the specified URL. We assume all
              // source maps are cacheable.
              staticFiles[urlPrefix + getItemPathname(item.sourceMapUrl)] = {
                path: item.sourceMap,
                absolutePath: path.join(clientDir, item.sourceMap),
                cacheable: true
              };
            }
          }
        });

        if (! clientJsonPath || ! clientDir || ! clientJson)
          throw new Error("Client config file not parsed.");

        return clientJson.manifest;
      };

      try {
        _.each(__meteor_bootstrap__.configJson.clientPaths, function (clientPath, arch) {
          archPath[arch] = path.dirname(clientPath);
          var manifest = getClientManifest(clientPath, arch);
          WebApp.clientPrograms[arch] = {
            manifest: manifest,
            version: calculateClientHash(manifest)
          };
        });

        // Exported for tests.
        WebAppInternals.staticFiles = staticFiles;
      } catch (e) {
        Log.error("Error reloading the client program: " + e.stack);
        process.exit(1);
      }
    });
  };

  WebAppInternals.generateBoilerplate = function () {
    syncQueue.runTask(function() {
      _.each(WebApp.clientPrograms, function (program, archName) {
        boilerplateByArch[archName] =
          generateBoilerplateInstance(archName, program.manifest);
      });

      // Configure CSS injection for the default arch
      // XXX implement the CSS injection for all archs?
      WebAppInternals.refreshableAssets = {
        allCss: boilerplateByArch[WebApp.defaultArch].baseData.css
      };
    });
  };

  WebAppInternals.reloadClientPrograms();

  // webserver
  var app = connect();

  // Auto-compress any json, javascript, or text.
  app.use(connect.compress());

  // Packages and apps can add handlers that run before any other Meteor
  // handlers via WebApp.rawConnectHandlers.
  var rawConnectHandlers = connect();
  app.use(rawConnectHandlers);

  // Strip off the path prefix, if it exists.
  app.use(function (request, response, next) {
    var pathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX;
    var url = Npm.require('url').parse(request.url);
    var pathname = url.pathname;
    // check if the path in the url starts with the path prefix (and the part
    // after the path prefix must start with a / if it exists.)
    if (pathPrefix && pathname.substring(0, pathPrefix.length) === pathPrefix &&
       (pathname.length == pathPrefix.length
        || pathname.substring(pathPrefix.length, pathPrefix.length + 1) === "/")) {
      request.url = request.url.substring(pathPrefix.length);
      next();
    } else if (pathname === "/favicon.ico" || pathname === "/robots.txt") {
      next();
    } else if (pathPrefix) {
      response.writeHead(404);
      response.write("Unknown path");
      response.end();
    } else {
      next();
    }
  });

  // Parse the query string into res.query. Used by oauth_server, but it's
  // generally pretty handy..
  app.use(connect.query());

  // Serve static files from the manifest.
  // This is inspired by the 'static' middleware.
  app.use(function (req, res, next) {
    Fiber(function () {
     WebAppInternals.staticFilesMiddleware(staticFiles, req, res, next);
    }).run();
  });

  // Packages and apps can add handlers to this via WebApp.connectHandlers.
  // They are inserted before our default handler.
  var packageAndAppHandlers = connect();
  app.use(packageAndAppHandlers);

  var suppressConnectErrors = false;
  // connect knows it is an error handler because it has 4 arguments instead of
  // 3. go figure.  (It is not smart enough to find such a thing if it's hidden
  // inside packageAndAppHandlers.)
  app.use(function (err, req, res, next) {
    if (!err || !suppressConnectErrors || !req.headers['x-suppress-error']) {
      next(err);
      return;
    }
    res.writeHead(err.status, { 'Content-Type': 'text/plain' });
    res.end("An error message");
  });

  app.use(function (req, res, next) {
    if (! appUrl(req.url))
      return next();

    var headers = {
      'Content-Type':  'text/html; charset=utf-8'
    };
    if (shuttingDown)
      headers['Connection'] = 'Close';

    var request = WebApp.categorizeRequest(req);

    if (request.url.query && request.url.query['meteor_css_resource']) {
      // In this case, we're requesting a CSS resource in the meteor-specific
      // way, but we don't have it.  Serve a static css file that indicates that
      // we didn't have it, so we can detect that and refresh.
      headers['Content-Type'] = 'text/css; charset=utf-8';
      res.writeHead(200, headers);
      res.write(".meteor-css-not-found-error { width: 0px;}");
      res.end();
      return undefined;
    }

    // /packages/asdfsad ... /cordova/dafsdf.js
    var pathname = connect.utils.parseUrl(req).pathname;
    var archKey = 'web.' + pathname.split('/')[1];
    if (!_.has(archPath, archKey)) {
      archKey = WebApp.defaultArch;
    }

    var boilerplate;
    try {
      boilerplate = getBoilerplate(request, archKey);
    } catch (e) {
      Log.error("Error running template: " + e);
      res.writeHead(500, headers);
      res.end();
      return undefined;
    }

    res.writeHead(200, headers);
    res.write(boilerplate);
    res.end();
    return undefined;
  });

  // Return 404 by default, if no other handlers serve this URL.
  app.use(function (req, res) {
    res.writeHead(404);
    res.end();
  });

  var httpServer = http.createServer(app);
  var onListeningCallbacks = [];

  // After 5 seconds w/o data on a socket, kill it.  On the other hand, if
  // there's an outstanding request, give it a higher timeout instead (to avoid
  // killing long-polling requests)
  httpServer.setTimeout(SHORT_SOCKET_TIMEOUT);

  // Do this here, and then also in livedata/stream_server.js, because
  // stream_server.js kills all the current request handlers when installing its
  // own.
  httpServer.on('request', WebApp._timeoutAdjustmentRequestCallback);

  // For now, handle SIGHUP here.  Later, this should be in some centralized
  // Meteor shutdown code.
  process.on('SIGHUP', Meteor.bindEnvironment(function () {
    shuttingDown = true;
    // tell others with websockets open that we plan to close this.
    // XXX: Eventually, this should be done with a standard meteor shut-down
    // logic path.
    httpServer.emit('meteor-closing');

    httpServer.close(Meteor.bindEnvironment(function () {
      if (proxy) {
        try {
          proxy.call('removeBindingsForJob', process.env.GALAXY_JOB);
        } catch (e) {
          Log.error("Error removing bindings: " + e.message);
          process.exit(1);
        }
      }
      process.exit(0);

    }, "On http server close failed"));

    // Ideally we will close before this hits.
    Meteor.setTimeout(function () {
      Log.warn("Closed by SIGHUP but one or more HTTP requests may not have finished.");
      process.exit(1);
    }, 5000);

  }, function (err) {
    console.log(err);
    process.exit(1);
  }));

  // start up app
  _.extend(WebApp, {
    connectHandlers: packageAndAppHandlers,
    rawConnectHandlers: rawConnectHandlers,
    httpServer: httpServer,
    // For testing.
    suppressConnectErrors: function () {
      suppressConnectErrors = true;
    },
    onListening: function (f) {
      if (onListeningCallbacks)
        onListeningCallbacks.push(f);
      else
        f();
    },
    // Hack: allow http tests to call connect.basicAuth without making them
    // Npm.depends on another copy of connect. (That would be fine if we could
    // have test-only NPM dependencies but is overkill here.)
    __basicAuth__: connect.basicAuth
  });

  // Let the rest of the packages (and Meteor.startup hooks) insert connect
  // middlewares and update __meteor_runtime_config__, then keep going to set up
  // actually serving HTML.
  main = function (argv) {
    // main happens post startup hooks, so we don't need a Meteor.startup() to
    // ensure this happens after the galaxy package is loaded.
    var AppConfig = Package["application-configuration"].AppConfig;
    // We used to use the optimist npm package to parse argv here, but it's
    // overkill (and no longer in the dev bundle). Just assume any instance of
    // '--keepalive' is a use of the option.
    var expectKeepalives = _.contains(argv, '--keepalive');
    WebAppInternals.generateBoilerplate();

    // only start listening after all the startup code has run.
    var localPort = parseInt(process.env.PORT) || 0;
    var host = process.env.BIND_IP;
    var localIp = host || '0.0.0.0';
    httpServer.listen(localPort, localIp, Meteor.bindEnvironment(function() {
      if (expectKeepalives)
        console.log("LISTENING"); // must match run-app.js
      var proxyBinding;

      AppConfig.configurePackage('webapp', function (configuration) {
        if (proxyBinding)
          proxyBinding.stop();
        if (configuration && configuration.proxy) {
          // TODO: We got rid of the place where this checks the app's
          // configuration, because this wants to be configured for some things
          // on a per-job basis.  Discuss w/ teammates.
          proxyBinding = AppConfig.configureService(
            "proxy",
            "pre0",
            function (proxyService) {
              if (proxyService && ! _.isEmpty(proxyService)) {
                var proxyConf;
                // XXX Figure out a per-job way to specify bind location
                // (besides hardcoding the location for ADMIN_APP jobs).
                if (process.env.ADMIN_APP) {
                  var bindPathPrefix = "";
                  if (process.env.GALAXY_APP !== "panel") {
                    bindPathPrefix = "/" + bindPathPrefix +
                      encodeURIComponent(
                        process.env.GALAXY_APP
                      ).replace(/\./g, '_');
                  }
                  proxyConf = {
                    bindHost: process.env.GALAXY_NAME,
                    bindPathPrefix: bindPathPrefix,
                    requiresAuth: true
                  };
                } else {
                  proxyConf = configuration.proxy;
                }
                Log("Attempting to bind to proxy at " +
                    proxyService);
                WebAppInternals.bindToProxy(_.extend({
                  proxyEndpoint: proxyService
                }, proxyConf));
              }
            }
          );
        }
      });

      var callbacks = onListeningCallbacks;
      onListeningCallbacks = null;
      _.each(callbacks, function (x) { x(); });

    }, function (e) {
      console.error("Error listening:", e);
      console.error(e && e.stack);
    }));

    if (expectKeepalives)
      initKeepalive();
    return 'DAEMON';
  };
};

var proxy;
WebAppInternals.bindToProxy = function (proxyConfig) {
  var securePort = proxyConfig.securePort || 4433;
  var insecurePort = proxyConfig.insecurePort || 8080;
  var bindPathPrefix = proxyConfig.bindPathPrefix || "";
  // XXX also support galaxy-based lookup
  if (!proxyConfig.proxyEndpoint)
    throw new Error("missing proxyEndpoint");
  if (!proxyConfig.bindHost)
    throw new Error("missing bindHost");
  if (!process.env.GALAXY_JOB)
    throw new Error("missing $GALAXY_JOB");
  if (!process.env.GALAXY_APP)
    throw new Error("missing $GALAXY_APP");
  if (!process.env.LAST_START)
    throw new Error("missing $LAST_START");

  // XXX rename pid argument to bindTo.
  // XXX factor out into a 'getPid' function in a 'galaxy' package?
  var pid = {
    job: process.env.GALAXY_JOB,
    lastStarted: +(process.env.LAST_START),
    app: process.env.GALAXY_APP
  };
  var myHost = os.hostname();

  WebAppInternals.usingDdpProxy = true;

  // This is run after packages are loaded (in main) so we can use
  // Follower.connect.
  if (proxy) {
    // XXX the concept here is that our configuration has changed and
    // we have connected to an entirely new follower set, which does
    // not have the state that we set up on the follower set that we
    // were previously connected to, and so we need to recreate all of
    // our bindings -- analogous to getting a SIGHUP and rereading
    // your configuration file. so probably this should actually tear
    // down the connection and make a whole new one, rather than
    // hot-reconnecting to a different URL.
    proxy.reconnect({
      url: proxyConfig.proxyEndpoint
    });
  } else {
    proxy = Package["follower-livedata"].Follower.connect(
      proxyConfig.proxyEndpoint, {
        group: "proxy"
      }
    );
  }

  var route = process.env.ROUTE;
  var ourHost = route.split(":")[0];
  var ourPort = +route.split(":")[1];

  var outstanding = 0;
  var startedAll = false;
  var checkComplete = function () {
    if (startedAll && ! outstanding)
      Log("Bound to proxy.");
  };
  var makeCallback = function () {
    outstanding++;
    return function (err) {
      if (err)
        throw err;
      outstanding--;
      checkComplete();
    };
  };

  // for now, have our (temporary) requiresAuth flag apply to all
  // routes created by this process.
  var requiresDdpAuth = !! proxyConfig.requiresAuth;
  var requiresHttpAuth = (!! proxyConfig.requiresAuth) &&
        (pid.app !== "panel" && pid.app !== "auth");

  // XXX a current limitation is that we treat securePort and
  // insecurePort as a global configuration parameter -- we assume
  // that if the proxy wants us to ask for 8080 to get port 80 traffic
  // on our default hostname, that's the same port that we would use
  // to get traffic on some other hostname that our proxy listens
  // for. Likewise, we assume that if the proxy can receive secure
  // traffic for our domain, it can assume secure traffic for any
  // domain! Hopefully this will get cleaned up before too long by
  // pushing that logic into the proxy service, so we can just ask for
  // port 80.

  // XXX BUG: if our configuration changes, and bindPathPrefix
  // changes, it appears that we will not remove the routes derived
  // from the old bindPathPrefix from the proxy (until the process
  // exits). It is not actually normal for bindPathPrefix to change,
  // certainly not without a process restart for other reasons, but
  // it'd be nice to fix.

  _.each(routes, function (route) {
    var parsedUrl = url.parse(route.url, /* parseQueryString */ false,
                              /* slashesDenoteHost aka workRight */ true);
    if (parsedUrl.protocol || parsedUrl.port || parsedUrl.search)
      throw new Error("Bad url");
    parsedUrl.host = null;
    parsedUrl.path = null;
    if (! parsedUrl.hostname) {
      parsedUrl.hostname = proxyConfig.bindHost;
      if (! parsedUrl.pathname)
        parsedUrl.pathname = "";
      if (! parsedUrl.pathname.indexOf("/") !== 0) {
        // Relative path
        parsedUrl.pathname = bindPathPrefix + parsedUrl.pathname;
      }
    }
    var version = "";

    var AppConfig = Package["application-configuration"].AppConfig;
    version = AppConfig.getStarForThisJob() || "";

    var parsedDdpUrl = _.clone(parsedUrl);
    parsedDdpUrl.protocol = "ddp";
    // Node has a hardcoded list of protocols that get '://' instead
    // of ':'. ddp needs to be added to that whitelist. Until then, we
    // can set the undocumented attribute 'slashes' to get the right
    // behavior. It's not clear whether than is by design or accident.
    parsedDdpUrl.slashes = true;
    parsedDdpUrl.port = '' + securePort;
    var ddpUrl = url.format(parsedDdpUrl);

    var proxyToHost, proxyToPort, proxyToPathPrefix;
    if (! _.has(route, 'forwardTo')) {
      proxyToHost = ourHost;
      proxyToPort = ourPort;
      proxyToPathPrefix = parsedUrl.pathname;
    } else {
      var parsedFwdUrl = url.parse(route.forwardTo, false, true);
      if (! parsedFwdUrl.hostname || parsedFwdUrl.protocol)
        throw new Error("Bad forward url");
      proxyToHost = parsedFwdUrl.hostname;
      proxyToPort = parseInt(parsedFwdUrl.port || "80");
      proxyToPathPrefix = parsedFwdUrl.pathname || "";
    }

    if (route.ddp) {
      proxy.call('bindDdp', {
        pid: pid,
        bindTo: {
          ddpUrl: ddpUrl,
          insecurePort: insecurePort
        },
        proxyTo: {
          tags: [version],
          host: proxyToHost,
          port: proxyToPort,
          pathPrefix: proxyToPathPrefix + '/websocket'
        },
        requiresAuth: requiresDdpAuth
      }, makeCallback());
    }

    if (route.http) {
      proxy.call('bindHttp', {
        pid: pid,
        bindTo: {
          host: parsedUrl.hostname,
          port: insecurePort,
          pathPrefix: parsedUrl.pathname
        },
        proxyTo: {
          tags: [version],
          host: proxyToHost,
          port: proxyToPort,
          pathPrefix: proxyToPathPrefix
        },
        requiresAuth: requiresHttpAuth
      }, makeCallback());

      // Only make the secure binding if we've been told that the
      // proxy knows how terminate secure connections for us (has an
      // appropriate cert, can bind the necessary port..)
      if (proxyConfig.securePort !== null) {
        proxy.call('bindHttp', {
          pid: pid,
          bindTo: {
            host: parsedUrl.hostname,
            port: securePort,
            pathPrefix: parsedUrl.pathname,
            ssl: true
          },
          proxyTo: {
            tags: [version],
            host: proxyToHost,
            port: proxyToPort,
            pathPrefix: proxyToPathPrefix
          },
          requiresAuth: requiresHttpAuth
        }, makeCallback());
      }
    }
  });

  startedAll = true;
  checkComplete();
};

// (Internal, unsupported interface -- subject to change)
//
// Listen for HTTP and/or DDP traffic and route it somewhere. Only
// takes effect when using a proxy service.
//
// 'url' is the traffic that we want to route, interpreted relative to
// the default URL where this app has been told to serve itself. It
// may not have a scheme or port, but it may have a host and a path,
// and if no host is provided the path need not be absolute. The
// following cases are possible:
//
//   //somehost.com
//     All incoming traffic for 'somehost.com'
//   //somehost.com/foo/bar
//     All incoming traffic for 'somehost.com', but only when
//     the first two path components are 'foo' and 'bar'.
//   /foo/bar
//     Incoming traffic on our default host, but only when the
//     first two path components are 'foo' and 'bar'.
//   foo/bar
//     Incoming traffic on our default host, but only when the path
//     starts with our default path prefix, followed by 'foo' and
//     'bar'.
//
// (Yes, these scheme-less URLs that start with '//' are legal URLs.)
//
// You can select either DDP traffic, HTTP traffic, or both. Both
// secure and insecure traffic will be gathered (assuming the proxy
// service is capable, eg, has appropriate certs and port mappings).
//
// With no 'forwardTo' option, the traffic is received by this process
// for service by the hooks in this 'webapp' package. The original URL
// is preserved (that is, if you bind "/a", and a user visits "/a/b",
// the app receives a request with a path of "/a/b", not a path of
// "/b").
//
// With 'forwardTo', the process is instead sent to some other remote
// host. The URL is adjusted by stripping the path components in 'url'
// and putting the path components in the 'forwardTo' URL in their
// place. For example, if you forward "//somehost/a" to
// "//otherhost/x", and the user types "//somehost/a/b" into their
// browser, then otherhost will receive a request with a Host header
// of "somehost" and a path of "/x/b".
//
// The routing continues until this process exits. For now, all of the
// routes must be set up ahead of time, before the initial
// registration with the proxy. Calling addRoute from the top level of
// your JS should do the trick.
//
// When multiple routes are present that match a given request, the
// most specific route wins. When routes with equal specificity are
// present, the proxy service will distribute the traffic between
// them.
//
// options may be:
// - ddp: if true, the default, include DDP traffic. This includes
//   both secure and insecure traffic, and both websocket and sockjs
//   transports.
// - http: if true, the default, include HTTP/HTTPS traffic.
// - forwardTo: if provided, should be a URL with a host, optional
//   path and port, and no scheme (the scheme will be derived from the
//   traffic type; for now it will always be a http or ws connection,
//   never https or wss, but we could add a forwardSecure flag to
//   re-encrypt).
var routes = [];
WebAppInternals.addRoute = function (url, options) {
  options = _.extend({
    ddp: true,
    http: true
  }, options || {});

  if (proxy)
    // In the future, lift this restriction
    throw new Error("Too late to add routes");

  routes.push(_.extend({ url: url }, options));
};

// Receive traffic on our default URL.
WebAppInternals.addRoute("");

runWebAppServer();

var inlineScriptsAllowed = true;

WebAppInternals.inlineScriptsAllowed = function () {
  return inlineScriptsAllowed;
};

WebAppInternals.setInlineScriptsAllowed = function (value) {
  inlineScriptsAllowed = value;
};

WebAppInternals.setBundledJsCssPrefix = function (prefix) {
  bundledJsCssPrefix = prefix;
};

// Packages can call `WebAppInternals.addStaticJs` to specify static
// JavaScript to be included in the app. This static JS will be inlined,
// unless inline scripts have been disabled, in which case it will be
// served under `/<sha1 of contents>`.
var additionalStaticJs = {};
WebAppInternals.addStaticJs = function (contents) {
  additionalStaticJs["/" + sha1(contents) + ".js"] = contents;
};

// Exported for tests
WebAppInternals.getBoilerplate = getBoilerplate;
WebAppInternals.additionalStaticJs = additionalStaticJs;


var semver = Npm.require('semver');

// Setup mock data for tests
var Packages = new LocalCollection;
var Versions = new LocalCollection;
var Builds = new LocalCollection;

var insertVersion = function (name, version, ecv, deps) {
  if (!Packages.findOne({name: name})) {
    Packages.insert({name: name});
  }

  var constructedDeps = {};
  _.each(deps, function (constraint, name) {
    constructedDeps[name] = {
      constraint: constraint,
      references: [
        { arch: "os", targetSlice: "main", weak: false,
          implied: false, unordered: false },
        { arch: "web", targetSlice: "main", weak: false,
          implied: false, unordered: false }]
    };
  });
  Versions.insert({ packageName: name, version: version,
                    earliestCompatibleVersion: ecv,
                    dependencies: constructedDeps });
  Builds.insert({ packageName: name, version: version,
                  buildArchitectures: "web+os" });
};
insertVersion("sparky-forms", "1.1.2", "1.0.0", {"forms": "=1.0.1", "sparkle": "=2.1.1"});
insertVersion("sparky-forms", "1.0.0", "1.0.0", {"awesome-dropdown": "=1.4.0"});
insertVersion("forms", "1.0.1", "1.0.0", {"sparkle": "2.1.0", "jquery-widgets": "1.0.0"});
insertVersion("sparkle", "2.1.0", "2.1.0", {"jquery": "1.8.2"});
insertVersion("sparkle", "2.1.1", "2.1.0", {"jquery": "1.8.2"});
insertVersion("sparkle", "1.0.0", "1.0.0");
insertVersion("awesome-dropdown", "1.4.0", "1.0.0", {"dropdown": "=1.2.2"});
insertVersion("awesome-dropdown", "1.5.0", "1.0.0", {"dropdown": "=1.2.2"});
insertVersion("dropdown", "1.2.2", "1.0.0", {"jquery-widgets": "1.0.0"});
insertVersion("jquery-widgets", "1.0.0", "1.0.0", {"jquery": "1.8.0", "sparkle": "2.1.1"});
insertVersion("jquery-widgets", "1.0.2", "1.0.0", {"jquery": "1.8.0", "sparkle": "2.1.1"});
insertVersion("jquery", "1.8.0", "1.8.0");
insertVersion("jquery", "1.8.2", "1.8.0");
insertVersion("foobar1", "1.0.0", "1.0.0", {foobar2: "=1.0.0"});
insertVersion("foobar2", "1.0.0", "1.0.0", {foobar1: "=1.0.0"});

// XXX Temporary hack: make a catalog stub to pass in to the constraint
// solver. We'll soon move constraint-solver into tools and just run
// tests with self-test, passing a real Catalog object.
var catalogStub = {
  packages: Packages,
  versions: Versions,
  builds: Builds,
  getAllPackageNames: function () {
    return _.pluck(Packages.find().fetch(), 'name');
  },
  getPackage: function (name) {
    return this.packages.findOne({ name: name });
  },
  getSortedVersions: function (name) {
    return _.pluck(
      this.versions.find({
        packageName: name
      }, { fields: { version: 1 } }).fetch(),
      'version'
    ).sort(semver.compare);
  },
  getVersion: function (name, version) {
    return this.versions.findOne({
      packageName: name,
      version: version
    });
  }
};

var resolver = new ConstraintSolver.PackagesResolver(catalogStub);

var currentTest = null;
var splitArgs = function (deps) {
  var dependencies = [], constraints = [];

  _.each(deps, function (constr, dep) {
    dependencies.push(dep);
    if (constr)
      constraints.push({ packageName: dep, type: (constr.indexOf("=") !== -1 ? "exactly" : "compatible-with"), version: constr.replace("=", "")});
  });
  return {dependencies: dependencies, constraints: constraints};
};

var t = function (deps, expected, options) {
  var dependencies = splitArgs(deps).dependencies;
  var constraints = splitArgs(deps).constraints;

  var resolvedDeps = resolver.resolve(dependencies, constraints, options);
  currentTest.equal(resolvedDeps, expected);
};

var t_progagateExact = function (deps, expected) {
  var dependencies = splitArgs(deps).dependencies;
  var constraints = splitArgs(deps).constraints;

  var resolvedDeps = resolver.propagateExactDeps(dependencies, constraints);
  currentTest.equal(resolvedDeps, expected);
};

var FAIL = function (deps, regexp) {
  currentTest.throws(function () {
    var dependencies = splitArgs(deps).dependencies;
    var constraints = splitArgs(deps).constraints;

    var resolvedDeps = resolver.resolve(dependencies, constraints);
  }, regexp);
};

Tinytest.add("constraint solver - exact dependencies", function (test) {
  currentTest = test;
  t_progagateExact({ "sparky-forms": "=1.1.2" }, { "sparky-forms": "1.1.2", "forms": "1.0.1", "sparkle": "2.1.1" });
  t_progagateExact({ "sparky-forms": "=1.1.2", "forms": "=1.0.1" }, { "sparky-forms": "1.1.2", "forms": "1.0.1", "sparkle": "2.1.1" });
  t_progagateExact({ "sparky-forms": "=1.1.2", "sparkle": "=2.1.1" }, { "sparky-forms": "1.1.2", "forms": "1.0.1", "sparkle": "2.1.1" });
  t_progagateExact({ "awesome-dropdown": "=1.5.0" }, { "awesome-dropdown": "1.5.0", "dropdown": "1.2.2" });
  t_progagateExact({ foobar1: "=1.0.0" }, {foobar1: "1.0.0", foobar2: "1.0.0"});

  FAIL({ "sparky-forms": "=1.1.2", "sparkle": "=1.0.0" }, /(.*sparkle.*sparky-forms.*)|(.*sparky-forms.*sparkle.*).*sparkle/);
  // something that isn't available for your architecture
  FAIL({ "sparky-forms": "=1.1.2", "sparkle": "=2.0.0" });
  FAIL({ "sparky-forms": "=0.0.1" });
  FAIL({ "sparky-forms-nonexistent": "0.0.1" }, /Cannot find anything about.*sparky-forms-nonexistent/);
});

Tinytest.add("constraint solver - simple exact + regular deps", function (test) {
  currentTest = test;

  t({ "sparky-forms": "=1.1.2" }, {
    "sparky-forms": "1.1.2",
    "forms": "1.0.1",
    "sparkle": "2.1.1",
    "jquery-widgets": "1.0.0",
    "jquery": "1.8.2"
  }, { _testing: true });

  t({ "sparky-forms": "=1.1.2", "awesome-dropdown": "=1.5.0" }, {
    "sparky-forms": "1.1.2",
    "forms": "1.0.1",
    "sparkle": "2.1.1",
    "jquery-widgets": "1.0.0",
    "jquery": "1.8.2",
    "awesome-dropdown": "1.5.0",
    "dropdown": "1.2.2"
  }, { _testing: true });
});

Tinytest.add("constraint solver - non-exact direct dependency", function (test) {
  currentTest = test;
  // sparky-forms 1.0.0 won't be chosen because it depends on a very old
  // jquery, which is not compatible with the jquery that
  // awesome-dropdown uses.
  t({ "sparky-forms": "1.0.0", "awesome-dropdown": "=1.5.0" }, {
    "sparky-forms": "1.1.2",
    "forms": "1.0.1",
    "sparkle": "2.1.1",
    "jquery-widgets": "1.0.0",
    "jquery": "1.8.2",
    "awesome-dropdown": "1.5.0",
    "dropdown": "1.2.2"
  }, { _testing: true });
});

Tinytest.add("constraint solver - no constraint dependency - anything", function (test) {
  currentTest = test;
  var versions = resolver.resolve(["sparkle"], [], { _testing: true });
  test.isTrue(_.isString(versions.sparkle));
});

Tinytest.add("constraint solver - no constraint dependency - transitive dep still picked right", function (test) {
  currentTest = test;
  var versions = resolver.resolve(["sparkle", "sparky-forms"], [{ packageName: "sparky-forms", version: "1.1.2", type: "compatible-with" }], { _testing: true });
  test.equal(versions.sparkle, "2.1.1");
});

var runBenchmarks = !!process.env.CONSTRAINT_SOLVER_BENCHMARK;

runBenchmarks && Tinytest.add("constraint solver - benchmark on gems - sinatra", function (test) {
  var r = new ConstraintSolver.PackagesResolver(getCatalogStub(sinatraGems));

  var args = splitArgs({
    'capistrano': '2.14.2',
    'data_mapper': '1.2.0',
    'dm-core': '1.2.0',
    'dm-sqlite-adapter': '1.2.0',
    'dm-timestamps': '1.2.0',
    'haml': '3.1.7',
    'sass': '3.2.1',
    'shotgun': '0.9.0',
    'sinatra': '1.3.5',
    'sqlite3': '1.3.7'
  });

  r.resolve(args.dependencies, args.constraints);
});

// Add a few versions that are referenced by other versions but don't exist. We
// now require referenced versions to exist.
railsGems.push({name: "bcrypt", number: "3.0.0", dependencies: []});
railsGems.push({name: "mime-types", number: "1.16.0", dependencies: []});
railsGems.push({"name":"pyu-ruby-sasl","number":"0.3.1","platform":"ruby","dependencies":[]});
railsGems.push({"name":"backports","number":"3.0.0","platform":"ruby","dependencies":[]});
railsGems.push({"name":"diff-lcs","number":"1.1.0","platform":"ruby","dependencies":[]});
var railsCatalog = getCatalogStub(railsGems);
runBenchmarks && Tinytest.add("constraint solver - benchmark on gems - rails", function (test) {
  var r = new ConstraintSolver.PackagesResolver(railsCatalog);

  var args = splitArgs({
    'rails': '4.0.4'
  });

  r.resolve(args.dependencies, args.constraints);
});

runBenchmarks && Tinytest.add("constraint solver - benchmark on gems - rails, gitlabhq", function (test) {
  var r = new ConstraintSolver.PackagesResolver(railsCatalog);

  var args = splitArgs({
    'rails': '4.0.0',
    'protected_attributes': null,
    'rails-observers': null,
    'actionpack-page_caching': null,
    'actionpack-action_caching': null,
    'default_value_for': '3.0.0',
    'mysql2': null,
    'devise': '3.0.4',
    'devise-async': '0.8.0',
    'omniauth': '1.1.3',
    'omniauth-google-oauth2': null,
    'omniauth-twitter': null,
    'omniauth-github': null,
    'gitlab_git': '5.7.1',
    'gitlab-grack': '2.0.0',
    'gitlab_omniauth-ldap': '1.0.4',
    'gitlab-gollum-lib': '1.1.0',
    'gitlab-linguist': '3.0.0',
    'grape': '0.6.1',
    'rack-cors': null,
    'email_validator': '1.4.0',
    'stamp': null,
    'enumerize': null,
    'kaminari': '0.15.1',
    'haml-rails': null,
    'carrierwave': null,
    'fog': '1.3.1',
    'six': null,
    'seed-fu': null,
    'redcarpet': '2.2.2',
    'github-markup': null,
    'asciidoctor': null,
    'unicorn': '4.6.3',
    'unicorn-worker-killer': null,
    'state_machine': null,
    'acts-as-taggable-on': null,
    'slim': null,
    'sinatra': null,
    'sidekiq': null,
    'httparty': null,
    'colored': null,
    'settingslogic': null,
    'foreman': null,
    'version_sorter': null,
    'redis-rails': null,
    'tinder': '1.9.2',
    'hipchat': '0.14.0',
    'gemnasium-gitlab-service': '0.2.1',
    'slack-notifier': '0.2.0',
    'd3_rails': '3.1.4',
    'underscore-rails': '1.4.4',
    'sanitize': null,
    'rack-attack': null,
    'ace-rails-ap': null,
    'sass-rails': null,
    'coffee-rails': null,
    'uglifier': null,
    'therubyracer': null,
    'turbolinks': null,
    'jquery-turbolinks': null,
    'select2-rails': null,
    'jquery-atwho-rails': '0.3.3',
    'jquery-rails': '2.1.3',
    'jquery-ui-rails': '2.0.2',
    'modernizr': '2.6.2',
    'raphael-rails': '2.1.2',
    'bootstrap-sass': '3.0.0',
    'font-awesome-rails': '3.2.0',
    'gitlab_emoji': '0.0.1',
    'gon': '5.0.0'
  });

  r.resolve(args.dependencies, args.constraints);
});

runBenchmarks && Tinytest.add("constraint solver - benchmark on gems - rails, gitlabhq, additions to the existing smaller solution", function (test) {
  var r = new ConstraintSolver.PackagesResolver(railsCatalog);

  var args = splitArgs({
    'rails': '4.0.0',
    'protected_attributes': null,
    'rails-observers': null,
    'actionpack-page_caching': null,
    'actionpack-action_caching': null,
    'default_value_for': '3.0.0',
    'mysql2': null,
    'devise': '3.0.4',
    'devise-async': '0.8.0',
    'omniauth': '1.1.3',
    'omniauth-google-oauth2': null,
    'omniauth-twitter': null,
    'omniauth-github': null,
    'gitlab_git': '5.7.1',
    'gitlab-grack': '2.0.0',
    'gitlab_omniauth-ldap': '1.0.4',
    'gitlab-gollum-lib': '1.1.0',
    'gitlab-linguist': '3.0.0',
    'grape': '0.6.1',
    'rack-cors': null,
    'email_validator': '1.4.0',
    'stamp': null,
    'enumerize': null,
    'kaminari': '0.15.1',
    'haml-rails': null,
    'carrierwave': null,
    'fog': '1.3.1',
    'six': null,
    'seed-fu': null,
    'redcarpet': '2.2.2',
    'github-markup': null,
    'asciidoctor': null,
    'unicorn': '4.6.3',
    'unicorn-worker-killer': null,
    'state_machine': null,
    'acts-as-taggable-on': null,
    'slim': null,
    'sinatra': null,
    'sidekiq': null,
    'httparty': null,
    'colored': null,
    'settingslogic': null,
    'foreman': null,
    'version_sorter': null,
    'redis-rails': null,
    'tinder': '1.9.2',
    'hipchat': '0.14.0',
    'gemnasium-gitlab-service': '0.2.1',
    'slack-notifier': '0.2.0',
    'd3_rails': '3.1.4',
    'underscore-rails': '1.4.4',
    'sanitize': null,
    'rack-attack': null,
    'ace-rails-ap': null,
    'sass-rails': null,
    'coffee-rails': null,
    'uglifier': null,
    'therubyracer': null,
    'turbolinks': null,
    'jquery-turbolinks': null,
    'select2-rails': null,
    'jquery-atwho-rails': '0.3.3',
    'jquery-rails': '2.1.3',
    'jquery-ui-rails': '2.0.2',
    'modernizr': '2.6.2',
    'raphael-rails': '2.1.2',
    'bootstrap-sass': '3.0.0',
    'font-awesome-rails': '3.2.0',
    'gitlab_emoji': '0.0.1',
    'gon': '5.0.0'
  });

  var previousSolution = {
    "actionmailer": "4.0.0",
    "actionpack": "4.0.0",
    "activemodel": "4.0.0",
    "activerecord": "4.0.0",
    "activerecord-deprecated_finders": "1.0.3",
    "activesupport": "4.0.0",
    "arel": "4.0.2",
    "asciidoctor": "0.1.4",
    "bcrypt": "3.1.7",
    "bcrypt-ruby": "3.1.5",
    "builder": "3.1.4",
    "carrierwave": "0.10.0",
    "coffee-rails": "4.0.1",
    "coffee-script": "2.2.0",
    "coffee-script-source": "1.7.0",
    "d3_rails": "3.1.4",
    "default_value_for": "3.0.0",
    "devise": "3.0.4",
    "devise-async": "0.8.0",
    "erubis": "2.7.0",
    "execjs": "2.0.2",
    "faraday": "0.9.0",
    "github-markup": "1.1.0",
    "haml": "4.0.5",
    "haml-rails": "0.5.1",
    "hashie": "2.0.3",
    "hike": "1.2.3",
    "httpauth": "0.2.1",
    "i18n": "0.6.9",
    "jquery-turbolinks": "2.0.2",
    "json": "1.8.1",
    "jwt": "0.1.10",
    "kaminari": "0.15.1",
    "mail": "2.5.4",
    "mime-types": "1.25.1",
    "minitest": "4.7.5",
    "multi_json": "1.9.0",
    "multipart-post": "2.0.0",
    "oauth": "0.4.7",
    "oauth2": "0.8.1",
    "omniauth": "1.1.4",
    "omniauth-github": "1.0.2",
    "omniauth-google-oauth2": "0.2.2",
    "omniauth-oauth": "1.0.1",
    "omniauth-oauth2": "1.1.1",
    "omniauth-twitter": "1.0.1",
    "orm_adapter": "0.5.0",
    "polyglot": "0.3.4",
    "posix-spawn": "0.3.8",
    "protected_attributes": "1.0.3",
    "rack": "1.5.2",
    "rack-test": "0.6.2",
    "rails": "4.0.0",
    "rails-observers": "0.1.2",
    "railties": "4.0.0",
    "rake": "10.1.1",
    "redcarpet": "2.2.2",
    "ref": "1.0.5",
    "sass": "3.2.17",
    "sass-rails": "4.0.2",
    "seed-fu": "2.3.0",
    "six": "0.2.0",
    "sprockets": "2.11.0",
    "sprockets-rails": "2.0.1",
    "therubyracer": "0.12.1",
    "thor": "0.19.1",
    "thread_safe": "0.3.1",
    "tilt": "1.4.1",
    "treetop": "1.4.15",
    "turbolinks": "2.2.0",
    "tzinfo": "0.3.39",
    "uglifier": "2.5.0",
    "warden": "1.2.3"
  };

  var solution = r.resolve(args.dependencies, args.constraints, { previousSolution: previousSolution });

  // check that root deps are the same
  _.each(args.dependencies, function (dep) {
    if (previousSolution[dep])
      test.equal(solution[dep], previousSolution[dep], dep);
  });
});

// Given a set of gems definitions returns a Catalog-like object
function getCatalogStub (gems) {
  return {
    getAllPackageNames: function () {
      return _.uniq(_.pluck(gems, 'name'));
    },
    getPackage: function (name) {
      throw new Error("Not implemeneted");
    },
    getSortedVersions: function (name) {
      return _.chain(gems)
        .filter(function (pv) { return pv.name === name; })
        .pluck('number')
        .map(function (version) {
          var nv = exactVersion(version);
          if (nv.length < version.length && version.split(".").length === 2)
            return version;
          return nv;
        })
        .filter(function (v) {
          return semver.valid(v);
        })
        .value()
        .sort(semver.compare);
    },
    getVersion: function (name, version) {
      var gem = _.find(gems, function (pv) {
        return pv.name === name && exactVersion(pv.number) === version;
      });

      var ecv = function (version) {
        // hard-code to "x.0.0"
        return parseInt(version) + ".0.0";
      };

      var packageVersion = {
        packageName: gem.name,
        version: gem.number,
        earliestCompatibleVersion: ecv(gem.number),
        dependencies: {}
      };

      _.each(gem.dependencies, function (dep) {
        var name = dep[0];
        var constraints = dep[1];

        packageVersion.dependencies[name] = {
          constraint: convertConstraints(constraints)[0], // XXX pick first one only
          references: [{
            "arch": "web"
          }, {
            "arch": "os" }]
        };
      });

      return packageVersion;
    }
  };
}

// Naively converts ruby-gems style constraints string to either exact
// constraint or a regular constraint.
function convertConstraints (inp) {
  var out = inp.split(",").map(function (s) {
    return s.trim();
  })
  // remove the constraints we don't support
  .filter(function (s) {
    return !/</g.test(s) && !/!=/.test(s);
  })
  // convert 1.2.3.beta2 => 1.2.3
  // and 0.2 => 0.2.0
  .map(function (s) {
    var x = s.split(" ");
    return [x[0], exactVersion(x[1])].join(" ");
  })
  // convert '= 1.2.3' => '=1.2.3'
  // '~>1.2.3' => '1.2.3'
  // '>=1.2.3' => '1.2.3'
  .map(function (s) {
    if (s.indexOf(">= 0") === 0)
      return "none";
    var x = s.split(' ');
    if (x[0] === '~>')
      x[0] = '';
    else if (x[0] === '>' || x[0] === '>=')
      x[0] = '>=';
    else if (x[0] === '=')
      x[0] = '=';
    else
      throw new Error('unknown operator: ' + x[0]);
    return x.join("");
  });

  return out;
}

function exactVersion (s) {
  s = s.match(/\d+(\.\d+(\.\d+)?)?/)[0];
  if (s.split('.').length < 3)
    s += ".0";
  if (s.split('.').length < 3)
    s += ".0";
  return s;
}


Package.describe({
  summary: "Ordered traversable dictionary with a mutable ordering",
  version: '1.0.0'
});

Package.on_use(function (api) {
  api.use('underscore');
  api.export('OrderedDict');
  api.add_files('ordered_dict.js', ['client', 'server']);
});


WebApp.calculateClientHashCordova = function () {


<template name="pkg_d3">
{{#markdown}}

## `d3`

[D3.js](http://d3js.org/) is a JavaScript library for manipulating
documents based on data. D3 helps you bring data to life using HTML, SVG
and CSS. D3's emphasis on web standards gives you the full capabilities
of modern browsers without tying yourself to a proprietary framework,
combining powerful visualization components and a data-driven approach
to DOM manipulation.

The `d3` package adds the D3 library to the client JavaScript
bundle. It has no effect on the server.

{{/markdown}}
</template>


var _ = require('underscore');
var Future = require('fibers/future');
var runLog = require('./run-log.js');

// options: listenPort, proxyToPort, proxyToHost, onFailure
var Proxy = function (options) {
  var self = this;

  self.listenPort = options.listenPort;
  self.listenHost = options.listenHost;
  // note: run-all.js updates proxyToPort directly
  self.proxyToPort = options.proxyToPort;
  self.proxyToHost = options.proxyToHost || '127.0.0.1';
  self.onFailure = options.onFailure || function () {};

  self.mode = "hold";
  self.httpQueue = []; // keys: req, res
  self.websocketQueue = []; // keys: req, socket, head

  self.proxy = null;
  self.server = null;
};

_.extend(Proxy.prototype, {
  // Start the proxy server, block (yield) until it is ready to go
  // (actively listening on outer and proxying to inner), and then
  // return.
  start: function () {
    var self = this;

    if (self.server)
      throw new Error("already running?");

    self.started = false;

    var http = require('http');
    var net = require('net');
    var httpProxy = require('http-proxy');

    self.proxy = httpProxy.createProxyServer({
      // agent is required to handle keep-alive, and http-proxy 1.0 is a little
      // buggy without it: https://github.com/nodejitsu/node-http-proxy/pull/488
      agent: new http.Agent({ maxSockets: 100 }),
      xfwd: true
    });

    var server = self.server = http.createServer(function (req, res) {
      // Normal HTTP request
      self.httpQueue.push({ req: req, res: res });
      self._tryHandleConnections();
    });

    self.server.on('upgrade', function (req, socket, head) {
      // Websocket connection
      self.websocketQueue.push({ req: req, socket: socket, head: head });
      self._tryHandleConnections();
    });

    var fut = new Future;
    self.server.on('error', function (err) {
      if (err.code === 'EADDRINUSE') {
        var port = self.listenPort;
        runLog.log(
"Can't listen on port " + port + ". Perhaps another Meteor is running?\n" +
"\n" +
"Running two copies of Meteor in the same application directory\n" +
"will not work. If something else is using port " + port + ", you can\n" +
"specify an alternative port with --port <port>.");
      } else if (self.listenHost &&
                 (err.code === 'ENOTFOUND' || err.code === 'EADDRNOTAVAIL')) {
        // This handles the case of "entered a DNS name that's unknown"
        // (ENOTFOUND from getaddrinfo) and "entered some random IP that we
        // can't bind to" (EADDRNOTAVAIL from listen).
        runLog.log(
"Can't listen on host " + self.listenHost + " (" + err.code + " from " +
            err.syscall + ").");

      } else {
        runLog.log('' + err);
      }
      self.onFailure();
      // Allow start() to return.
      fut.isResolved() || fut['return']();
    });

    // Don't crash if the app doesn't respond. instead return an error
    // immediately. This shouldn't happen much since we try to not
    // send requests if the app is down.
    //
    // Currently, this error is emitted if the proxy->server connection has an
    // error (whether in HTTP or websocket proxying).  It is not emitted if the
    // client->proxy connection has an error, though this may change; see
    // discussion at https://github.com/nodejitsu/node-http-proxy/pull/488
    self.proxy.on('error', function (err, req, resOrSocket) {
      if (resOrSocket instanceof http.ServerResponse) {
        resOrSocket.writeHead(503, {
          'Content-Type': 'text/plain'
        });
        resOrSocket.end('Unexpected error.');
      } else if (resOrSocket instanceof net.Socket) {
        resOrSocket.end();
      }
    });

    self.server.listen(self.listenPort, self.listenHost || '0.0.0.0', function () {
      if (self.server) {
        self.started = true;
      } else {
        // stop() got called while we were invoking listen! Close the server (we
        // still have the var server). The rest of the cleanup shouldn't be
        // necessary.
        server.close();
      }
      fut.isResolved() || fut['return']();
    });

    fut.wait();
  },

  // Idempotent.
  stop: function () {
    var self = this;

    if (! self.server)
      return;

    if (! self.started) {
      // This probably means that we failed to listen. However, there could be a
      // race condition and we could be in the middle of starting to listen! In
      // that case, the listen callback will notice that we nulled out server
      // here.
      self.server = null;
      return;
    }

    // This stops listening but allows existing connections to
    // complete gracefully.
    self.server.close();
    self.server = null;

    // It doesn't seem to be necessary to do anything special to
    // destroy an httpProxy proxyserver object.
    self.proxy = null;

    // Drop any held connections.
    _.each(self.httpQueue, function (c) {
      c.res.statusCode = 500;
      c.res.end();
    });
    self.httpQueue = [];

    _.each(self.websocketQueue, function (c) {
      c.socket.destroy();
    });
    self.websocketQueue = [];

    self.mode = "hold";
  },

  _tryHandleConnections: function () {
    var self = this;

    while (self.httpQueue.length) {
      if (self.mode !== "errorpage" && self.mode !== "proxy")
        break;

      var c = self.httpQueue.shift();
      if (self.mode === "errorpage") {
        // XXX serve an app that shows the logs nicely and that also
        // knows how to reload when the server comes back up
        c.res.writeHead(200, {'Content-Type': 'text/plain'});
        c.res.write("Your app is crashing. Here's the latest log.\n\n");

        _.each(runLog.getLog(), function (item) {
          c.res.write(item.message + "\n");
        });

        c.res.end();
      } else {
        self.proxy.web(c.req, c.res, {
          target: 'http://' + self.proxyToHost + ':' + self.proxyToPort
        });
      }
    }

    while (self.websocketQueue.length) {
      if (self.mode !== "proxy")
        break;

      var c = self.websocketQueue.shift();
      self.proxy.ws(c.req, c.socket, c.head, {
        target: 'http://' + self.proxyToHost + ':' + self.proxyToPort
      });
    }
  },

  // The proxy can be in one of three modes:
  // - "hold": hold connections until the mode changes
  // - "proxy": connections are proxied to the configured port
  // - "errorpage": an error page is served to HTTP connections, and
  //   websocket connections are held
  //
  // The initial mode is "hold".
  setMode: function (mode) {
    var self = this;
    self.mode = mode;
    self._tryHandleConnections();
  }
});

exports.Proxy = Proxy;


options.production || process.env.NODE_ENV === 'production';

'/' + Path.basename(options.outDir || 'dist');

typeof options.watch === 'boolean' ? options.watch : !isProduction;

const target = options.target || 'browser';

outDir: Path.resolve(options.outDir || 'dist'),

keep keep keep keep keep replace replace replace replace keep keep keep keep keep

const publicURL = options.publicUrl || options.publicURL || '/';

const assert = require('assert');
const fs = require('fs');
const {bundle, assertBundleTree} = require('./utils');

describe('html', function() {
  it('should support bundling HTML', async function() {
    let b = await bundle(__dirname + '/integration/html/index.html');

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.html'],
      childBundles: [
        {
          type: 'png',
          assets: ['100x100.png'],
          childBundles: []
        },
        {
          type: 'svg',
          assets: ['icons.svg'],
          childBundles: []
        },
        {
          type: 'css',
          assets: ['index.css'],
          childBundles: []
        },
        {
          type: 'js',
          assets: ['index.js'],
          childBundles: [
            {
              type: 'map'
            }
          ]
        },
        {
          type: 'html',
          assets: ['other.html'],
          childBundles: [
            {
              type: 'css',
              assets: ['index.css'],
              childBundles: []
            },
            {
              type: 'js',
              assets: ['index.js'],
              childBundles: [
                {
                  type: 'map'
                }
              ]
            }
          ]
        }
      ]
    });

    let files = fs.readdirSync(__dirname + '/dist');
    let html = fs.readFileSync(__dirname + '/dist/index.html');
    for (let file of files) {
      let ext = file.match(/\.([0-9a-z]+)(?:[?#]|$)/i)[0];
      if (file !== 'index.html' && ext !== '.map') {
        assert(html.includes(file));
      }
    }
  });

  it('should find href attr when not first', async function() {
    let b = await bundle(__dirname + '/integration/html-attr-order/index.html');

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.html'],
      childBundles: [
        {
          type: 'html',
          assets: ['other.html'],
          childBundles: []
        }
      ]
    });
  });

  it('should support transforming HTML with posthtml', async function() {
    let b = await bundle(__dirname + '/integration/posthtml/index.html');

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.html'],
      childBundles: []
    });

    let html = fs.readFileSync(__dirname + '/dist/index.html');
    assert(html.includes('
Other page
'));
  });

  it('should find assets inside posthtml', async function() {
    let b = await bundle(__dirname + '/integration/posthtml-assets/index.html');

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.html'],
      childBundles: [
        {
          type: 'js',
          assets: ['index.js'],
          childBundles: [
            {
              type: 'map'
            }
          ]
        }
      ]
    });
  });

  it('should insert sibling CSS bundles for JS files in the HEAD', async function() {
    let b = await bundle(__dirname + '/integration/html-css/index.html');

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.html'],
      childBundles: [
        {
          type: 'js',
          assets: ['index.js', 'index.css'],
          childBundles: [
            {
              type: 'css',
              assets: ['index.css'],
              childBundles: []
            },
            {
              type: 'map'
            }
          ]
        }
      ]
    });

    let html = fs.readFileSync(__dirname + '/dist/index.html');
    assert(
      /<link rel="stylesheet" href="[/\\]{1}dist[/\\]{1}html-css\.[a-f0-9]+\.css">/.test(
        html
      )
    );
  });

  it('should insert sibling bundles before body element if no HEAD', async function() {
    let b = await bundle(__dirname + '/integration/html-css-head/index.html');

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.html'],
      childBundles: [
        {
          type: 'js',
          assets: ['index.js', 'index.css'],
          childBundles: [
            {
              type: 'css',
              assets: ['index.css'],
              childBundles: []
            },
            {
              type: 'map'
            }
          ]
        }
      ]
    });

    let html = fs.readFileSync(__dirname + '/dist/index.html');
    assert(
      /\s*<link rel="stylesheet" href="[/\\]{1}dist[/\\]{1}html-css-head\.[a-f0-9]+\.css">\s*/.test(
        html
      )
    );
  });

  it('should insert sibling JS bundles for CSS files in the HEAD', async function() {
    let b = await bundle(__dirname + '/integration/html-css-js/index.html', {
      hmr: true
    });

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.html'],
      childBundles: [
        {
          type: 'css',
          assets: ['index.css'],
          childBundles: [
            {
              type: 'map'
            },
            {
              type: 'js',
              assets: [
                'index.css',
                'bundle-url.js',
                'css-loader.js',
                'hmr-runtime.js'
              ],
              childBundles: []
            }
          ]
        }
      ]
    });

    let html = fs.readFileSync(__dirname + '/dist/index.html');
    assert(
      /')
    );

    // minifySvg is false
    assert(
      html.includes(
        '<svg version="1.1" baseprofile="full" width="300" height="200" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="red"></rect><circle cx="150" cy="100" r="80" fill="green"></circle><text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text></svg>'
      )
    );
  });

  it('should not prepend the public path to assets with remote URLs', async function() {
    await bundle(__dirname + '/integration/html/index.html');

    let html = fs.readFileSync(__dirname + '/dist/index.html', 'utf8');
    assert(
      html.includes('')
    );
  });

  it('should not prepend the public path to hash links', async function() {
    await bundle(__dirname + '/integration/html/index.html');

    let html = fs.readFileSync(__dirname + '/dist/index.html', 'utf8');
    assert(html.includes(''));
  });

  it('should detect virtual paths', async function() {
    let b = await bundle(
      __dirname + '/integration/html-virtualpath/index.html'
    );

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.html'],
      childBundles: [
        {
          type: 'html',
          assets: ['other.html'],
          childBundles: []
        }
      ]
    });
  });

  it('should not update root/main file in the bundles', async function() {
    await bundle(__dirname + '/integration/html-root/index.html');

    let files = fs.readdirSync(__dirname + '/dist');

    for (let file of files) {
      if (file !== 'index.html' && file.endsWith('.html')) {
        let html = fs.readFileSync(__dirname + '/dist/' + file);
        assert(html.includes('index.html'));
      }
    }
  });

  it('should conserve the spacing in the HTML tags', async function() {
    await bundle(__dirname + '/integration/html/index.html', {
      production: true
    });

    let html = fs.readFileSync(__dirname + '/dist/index.html', 'utf8');
    assert(/hello<\/i> world<\/i>/.test(html));
  });

  it('should support child bundles of different types', async function() {
    let b = await bundle(
      __dirname + '/integration/child-bundle-different-types/index.html'
    );

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.html'],
      childBundles: [
        {
          type: 'js',
          assets: ['main.js', 'util.js', 'other.js'],
          childBundles: [
            {
              type: 'map'
            }
          ]
        },
        {
          type: 'html',
          assets: ['other.html'],
          childBundles: [
            {
              type: 'js',
              assets: ['index.js', 'util.js', 'other.js'],
              childBundles: [
                {
                  type: 'map'
                }
              ]
            }
          ]
        }
      ]
    });
  });

  it('should support circular dependencies', async function() {
    let b = await bundle(__dirname + '/integration/circular/index.html');

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.html'],
      childBundles: [
        {
          type: 'html',
          assets: ['about.html'],
          childBundles: [
            {
              type: 'js',
              assets: ['about.js', 'index.js'],
              childBundles: [
                {
                  type: 'map'
                }
              ]
            },
            {
              type: 'html',
              assets: ['test.html'],
              childBundles: []
            }
          ]
        },
        {
          type: 'js',
          assets: ['about.js', 'index.js'],
          childBundles: [
            {
              type: 'map'
            }
          ]
        }
      ]
    });
  });

  it('should support bundling HTM', async function() {
    let b = await bundle(__dirname + '/integration/htm-extension/index.htm');

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.htm'],
      type: 'html',
      childBundles: [
        {
          type: 'js',
          assets: ['index.js'],
          childBundles: [
            {
              type: 'map'
            }
          ]
        }
      ]
    });
  });

  it('should detect srcset attribute', async function() {
    let b = await bundle(__dirname + '/integration/html-srcset/index.html');

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.html'],
      childBundles: [
        {
          type: 'png',
          assets: ['100x100.png'],
          childBundles: []
        },
        {
          type: 'png',
          assets: ['200x200.png'],
          childBundles: []
        },
        {
          type: 'png',
          assets: ['300x300.png'],
          childBundles: []
        }
      ]
    });
  });

  it('should detect srcset attribute of source element', async function() {
    let b = await bundle(
      __dirname + '/integration/html-source-srcset/index.html'
    );

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.html'],
      childBundles: [
        {
          type: 'png',
          assets: ['100x100.png'],
          childBundles: []
        },
        {
          type: 'png',
          assets: ['200x200.png'],
          childBundles: []
        },
        {
          type: 'png',
          assets: ['300x300.png'],
          childBundles: []
        }
      ]
    });
  });

  it('should support webmanifest', async function() {
    let b = await bundle(__dirname + '/integration/webmanifest/index.html');

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.html'],
      childBundles: [
        {
          type: 'webmanifest',
          assets: ['manifest.webmanifest'],
          childBundles: [
            {
              type: 'txt',
              assets: ['some.txt'],
              childBundles: []
            }
          ]
        }
      ]
    });
  });

  it('should bundle svg files correctly', async function() {
    let b = await bundle(__dirname + '/integration/html-svg/index.html');

    assertBundleTree(b, {
      name: 'index.html',
      assets: ['index.html'],
      childBundles: [
        {
          type: 'svg',
          assets: ['file.svg']
        }
      ]
    });
  });
});


const crypto = require('crypto');
const fs = require('fs');

function md5(string) {
  return crypto
    .createHash('md5')
    .update(string)
    .digest('hex');
}

md5.file = function(filename) {
  return new Promise((resolve, reject) => {
    fs
      .createReadStream(filename)
      .pipe(crypto.createHash('md5').setEncoding('hex'))
      .on('finish', function() {
        resolve(this.read());
      })
      .on('error', reject);
  });
};

module.exports = md5;


const types = require('babel-types');

// from babel-types. remove when we upgrade to babel 7.
// https://github.com/babel/babel/blob/0189b387026c35472dccf45d14d58312d249f799/packages/babel-types/src/index.js#L347
module.exports = function matchesPattern(member, match, allowPartial) {
  // not a member expression
  if (!types.isMemberExpression(member)) return false;

  const parts = Array.isArray(match) ? match : match.split('.');
  const nodes = [];

  let node;
  for (node = member; types.isMemberExpression(node); node = node.object) {
    nodes.push(node.property);
  }
  nodes.push(node);

  if (nodes.length < parts.length) return false;
  if (!allowPartial && nodes.length > parts.length) return false;

  for (let i = 0, j = nodes.length - 1; i < parts.length; i++, j--) {
    const node = nodes[j];
    let value;
    if (types.isIdentifier(node)) {
      value = node.name;
    } else if (types.isStringLiteral(node)) {
      value = node.value;
    } else {
      return false;
    }

    if (parts[i] !== value) return false;
  }

  return true;
};


<!---
Thanks for filing an issue 😄 ! Before you submit, please read the following:

Search open/closed issues before submitting since someone might have asked the same thing before!
-->

**Choose one:** is this a 🐛 bug report or 🙋 feature request?

<!--- Provide a general summary of the issue in the title above -->

### 🎛 Configuration (.babelrc, package.json, cli command)

<!--- If describing a bug, tell us what your babel configuration looks like -->

```js
{
  "your": { "config": "here" }
}
```

### 🤔 Expected Behavior

<!--- If you're describing a bug, tell us what should happen -->

<!--- If you're suggesting a change/improvement, tell us how it should work -->

### 😯 Current Behavior

<!--- If describing a bug, tell us what happens instead of the expected behavior -->

<!--- If you are seeing an error, please include the full error message and stack trace -->

<!--- If suggesting a change/improvement, explain the difference from current behavior -->

### 💁 Possible Solution

<!--- Not obligatory, but suggest a fix/reason for the bug, -->

<!--- or ideas how to implement the addition or change -->

### 🔦 Context

<!--- How has this issue affected you? What are you trying to accomplish? -->

<!--- Providing context helps us come up with a solution that is most useful in the real world -->

### 💻 Code Sample

<!-- If you are seeing an error, please provide a code repository, gist or sample files to reproduce the issue -->

### 🌍 Your Environment

<!--- Include as many relevant details about the environment you experienced the bug in -->

| Software         | Version(s) |
| ---------------- | ---------- |
| Parcel           |
| Node             |
| npm/Yarn         |
| Operating System |

<!-- Love parcel? Please consider supporting our collective:
👉  https://opencollective.com/parcel/donate -->


let html = fs.readFileSync(__dirname + '/dist/index.html', 'utf8');

assert(fs.existsSync(__dirname + '/dist/' + filename));

keep keep keep keep replace keep keep keep keep keep

const assert = require('assert');
const fs = require('fs');
const {bundler} = require('./utils');
const http = require('http');
const https = require('https');

describe('server', function() {
  let server;
  afterEach(function() {
    if (server) {
      server.close();
      server = null;
    }
  });

  function get(file, client = http) {
    return new Promise((resolve, reject) => {
      client.get(
        {
          hostname: 'localhost',
          port: server.address().port,
          path: file,
          rejectUnauthorized: false
        },
        res => {
          if (res.statusCode !== 200) {
            return reject(new Error('Request failed: ' + res.statusCode));
          }

          res.setEncoding('utf8');
          let data = '';
          res.on('data', c => (data += c));
          res.on('end', () => {
            resolve(data);
          });
        }
      );
    });
  }

  it('should serve files', async function() {
    let b = bundler(__dirname + '/integration/commonjs/index.js');
    server = await b.serve(0);

    let data = await get('/dist/index.js');
    assert.equal(data, fs.readFileSync(__dirname + '/dist/index.js', 'utf8'));
  });

  it('should serve a default page if the main bundle is an HTML asset', async function() {
    let b = bundler(__dirname + '/integration/html/index.html');
    server = await b.serve(0);

    let data = await get('/');
    assert.equal(data, fs.readFileSync(__dirname + '/dist/index.html', 'utf8'));

    data = await get('/foo/bar');
    assert.equal(data, fs.readFileSync(__dirname + '/dist/index.html', 'utf8'));
  });

  it('should serve a 404 if the file does not exist', async function() {
    let b = bundler(__dirname + '/integration/commonjs/index.js');
    server = await b.serve(0);

    let threw = false;
    try {
      await get('/dist/fake.js');
    } catch (err) {
      threw = true;
    }

    assert(threw);
  });

  it('should serve a 500 if the bundler errored', async function() {
    let b = bundler(__dirname + '/integration/html/index.html');
    server = await b.serve(0);

    b.errored = true;

    try {
      await get('/');
      throw new Error('GET / responded with 200');
    } catch (err) {
      assert.equal(err.message, 'Request failed: 500');
    }

    b.errored = false;
    await get('/');
  });

  it('should support HTTPS', async function() {
    let b = bundler(__dirname + '/integration/commonjs/index.js');
    server = await b.serve(0, true);

    let data = await get('/dist/index.js', https);
    assert.equal(data, fs.readFileSync(__dirname + '/dist/index.js', 'utf8'));
  });

  it('should support HTTPS via custom certificate', async function() {
    let b = bundler(__dirname + '/integration/commonjs/index.js');
    server = await b.serve(0, {
      key: __dirname + '/integration/https/private.pem',
      cert: __dirname + '/integration/https/primary.crt'
    });

    let data = await get('/dist/index.js', https);
    assert.equal(data, fs.readFileSync(__dirname + '/dist/index.js', 'utf8'));
  });

  it('should serve static assets as well as html', async function() {
    let b = bundler(__dirname + '/integration/html/index.html', {
      publicUrl: '/'
    });
    server = await b.serve(0);
    // When accessing / we should get the index page.
    let data = await get('/');
    assert.equal(data, fs.readFileSync(__dirname + '/dist/index.html', 'utf8'));
    // When accessing /hello.txt we should get txt document.
    fs.writeFileSync(__dirname + '/dist/hello.txt', 'hello');
    data = await get('/hello.txt');
    assert.equal(data, 'hello');
  });
});


var fs = require('fs');
module.exports = fs.readFileSync(__dirname + '/test.txt');


<!doctype html>

  <link rel="stylesheet" href="index.cSs" />

Hello world

Linking to another page

  Hash link
  Mailto link
  Tel link

  hello world
  <svg><use xlink:href="icons.SVG#icon-repo-pull"></use></svg>


html = fs.readFileSync(__dirname + '/dist/index.html', 'utf8');

assert(fs.existsSync(__dirname + '/dist/' + newFilename));

keep keep keep keep keep replace keep keep keep keep keep


const Asset = require('../Asset');
const toml = require('toml');
const serializeObject = require('../utils/serializeObject');

class TOMLAsset extends Asset {
  constructor(name, pkg, options) {
    super(name, pkg, options);
    this.type = 'js';
  }

  parse(code) {
    return toml.parse(code);
  }

  generate() {
    return {
      js: serializeObject(this.ast, this.options.minify)
    };
  }
}

module.exports = TOMLAsset;


const promisify = require('./promisify');
const fs = require('fs');
const mkdirp = require('mkdirp');

exports.readFile = promisify(fs.readFile);
exports.writeFile = promisify(fs.writeFile);
exports.stat = promisify(fs.stat);
exports.readdir = promisify(fs.readdir);
exports.unlink = promisify(fs.unlink);

exports.exists = function(filename) {
  return new Promise(resolve => {
    fs.exists(filename, resolve);
  });
};

exports.mkdirp = promisify(mkdirp);


await bundle(__dirname + '/input/index.js', {

let js = fs.readFileSync(__dirname + '/dist/index.js', 'utf8');

let filename = js.match(/\/dist\/(test\.[0-9a-f]+\.txt)/)[1];

fs.writeFileSync(__dirname + '/input/test.txt', 'hello world');

let filename = js.match(/\/(test\.[0-9a-f]+\.txt)/)[1];


<!doctype html>

  <title>Test</title>

Other page


let newFilename = js.match(/\/dist\/(test\.[0-9a-f]+\.txt)/)[1];

keep keep keep replace keep keep keep keep keep

let newFilename = js.match(/\/(test\.[0-9a-f]+\.txt)/)[1];

const assert = require('assert');
const fs = require('fs');
const {bundle, run, assertBundleTree} = require('./utils');

describe('typescript', function() {
  it('should produce a ts bundle using ES6 imports', async function() {
    let b = await bundle(__dirname + '/integration/typescript/index.ts');

    assert.equal(b.assets.size, 2);
    assert.equal(b.childBundles.size, 1);

    let output = run(b);
    assert.equal(typeof output.count, 'function');
    assert.equal(output.count(), 3);
  });

  it('should produce a ts bundle using commonJS require', async function() {
    let b = await bundle(
      __dirname + '/integration/typescript-require/index.ts'
    );

    assert.equal(b.assets.size, 2);
    assert.equal(b.childBundles.size, 1);

    let output = run(b);
    assert.equal(typeof output.count, 'function');
    assert.equal(output.count(), 3);
  });

  it('should support json require', async function() {
    let b = await bundle(__dirname + '/integration/typescript-json/index.ts');

    assert.equal(b.assets.size, 2);
    assert.equal(b.childBundles.size, 1);

    let output = run(b);
    assert.equal(typeof output.count, 'function');
    assert.equal(output.count(), 3);
  });

  it('should support env variables', async function() {
    let b = await bundle(__dirname + '/integration/typescript-env/index.ts');

    assert.equal(b.assets.size, 1);
    assert.equal(b.childBundles.size, 1);

    let output = run(b);
    assert.equal(typeof output.env, 'function');
    assert.equal(output.env(), 'test');
  });

  it('should support importing a URL to a raw asset', async function() {
    let b = await bundle(__dirname + '/integration/typescript-raw/index.ts');

    assertBundleTree(b, {
      name: 'index.js',
      assets: ['index.ts', 'test.txt'],
      childBundles: [
        {
          type: 'map'
        },
        {
          type: 'txt',
          assets: ['test.txt'],
          childBundles: []
        }
      ]
    });

    let output = run(b);
    assert.equal(typeof output.getRaw, 'function');
    assert(/^\/dist\/test\.[0-9a-f]+\.txt$/.test(output.getRaw()));
    assert(fs.existsSync(__dirname + output.getRaw()));
  });

  it('should minify in production mode', async function() {
    let b = await bundle(
      __dirname + '/integration/typescript-require/index.ts',
      {production: true}
    );

    assert.equal(b.assets.size, 2);
    assert.equal(b.childBundles.size, 1);

    let output = run(b);
    assert.equal(typeof output.count, 'function');
    assert.equal(output.count(), 3);

    let js = fs.readFileSync(__dirname + '/dist/index.js', 'utf8');
    assert(!js.includes('local.a'));
  });

  it('should support loading tsconfig.json', async function() {
    let b = await bundle(__dirname + '/integration/typescript-config/index.ts');

    let output = run(b);
    assert.equal(output, 2);

    let js = fs.readFileSync(__dirname + '/dist/index.js', 'utf8');
    assert(!js.includes('/* test comment */'));
  });

  it('should support compiling JSX', async function() {
    await bundle(__dirname + '/integration/typescript-jsx/index.tsx');

    let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8');
    assert(file.includes('React.createElement("div"'));
  });

  it('should use esModuleInterop by default', async function() {
    let b = await bundle(
      __dirname + '/integration/typescript-interop/index.ts'
    );

    assertBundleTree(b, {
      name: 'index.js',
      assets: ['index.ts', 'commonjs-module.js'],
      childBundles: [
        {
          type: 'map'
        }
      ]
    });

    let output = run(b);
    assert.equal(typeof output.test, 'function');
    assert.equal(output.test(), 'test passed');
  });
});


const assert = require('assert');
const urlJoin = require('../src/utils/urlJoin');

describe('Url Join', () => {
  it('should join a filename with a URL', () => {
    assert.equal(
      urlJoin('https://parceljs.org', 'a.js'),
      'https://parceljs.org/a.js'
    );
  });

  it('should join a path with a URL', () => {
    assert.equal(
      urlJoin('https://parceljs.org', 'bar/a.js'),
      'https://parceljs.org/bar/a.js'
    );
  });

  it('should join a paths together', () => {
    assert.equal(
      urlJoin('https://parceljs.org/foo/', 'bar/a.js'),
      'https://parceljs.org/foo/bar/a.js'
    );
  });

  it('should join an absolute path with a URL', () => {
    assert.equal(
      urlJoin('https://parceljs.org/foo/', '/bar/a.js'),
      'https://parceljs.org/foo/bar/a.js'
    );
  });

  it('should join a URL with a querystring', () => {
    assert.equal(
      urlJoin('https://parceljs.org/foo', '/bar/a.js?a=123'),
      'https://parceljs.org/foo/bar/a.js?a=123'
    );

    assert.equal(
      urlJoin('https://parceljs.org/foo', '/bar/a.js?a=123&b=456'),
      'https://parceljs.org/foo/bar/a.js?a=123&b=456'
    );
  });

  it('should join a URL with a hash', () => {
    assert.equal(
      urlJoin('https://parceljs.org/foo', '/bar/a.js#hello'),
      'https://parceljs.org/foo/bar/a.js#hello'
    );

    assert.equal(
      urlJoin('https://parceljs.org/foo', '/bar/a.js?a=123&b=456#hello'),
      'https://parceljs.org/foo/bar/a.js?a=123&b=456#hello'
    );
  });

  it('should join two paths together', () => {
    assert.equal(
      urlJoin('/Users/people/projects/parcel', '/bar/foo.js'),
      '/Users/people/projects/parcel/bar/foo.js'
    );
  });

  it('should support windows paths', () => {
    assert.equal(urlJoin('dist\\foo', '\\bar\\foo.js'), 'dist/foo/bar/foo.js');
  });

  it('should parse double slashes as host', () => {
    assert.equal(
      urlJoin('//parceljs.org/foo', 'bar/a.js?a=123&b=456#hello'),
      '//parceljs.org/foo/bar/a.js?a=123&b=456#hello'
    );
  });
});


navigator.serviceWorker.register('worker.js', { scope: './' });


let html = fs.readFileSync(__dirname + '/dist/index.html');


#version 300 es

#pragma glslify: test = require('~/lib')

precision mediump float;

void main() {
  test();
}


require('v8-compile-cache');
const chalk = require('chalk');
const program = require('commander');
const version = require('../package.json').version;

program.version(version);

program
  .command('serve [input]')
  .description('starts a development server')
  .option(
    '-p, --port <port>',
    'set the port to serve on. defaults to 1234',
    parseInt
  )
  .option(
    '--hmr-port <port>',
    'set the port to serve HMR websockets, defaults to random',
    parseInt
  )
  .option(
    '--hmr-hostname <hostname>',
    'set the hostname of HMR websockets, defaults to location.hostname of current window'
  )
  .option('--https', 'serves files over HTTPS')
  .option('--cert <path>', 'path to certificate to use with HTTPS')
  .option('--key <path>', 'path to private key to use with HTTPS')
  .option(
    '--open [browser]',
    'automatically open in specified browser, defaults to default browser'
  )
  .option(
    '-d, --out-dir <path>',
    'set the output directory. defaults to "dist"'
  )
  .option(
    '-o, --out-file <filename>',
    'set the output filename for the application entry point.'
  )
  .option(
    '--public-url <url>',
    'set the public URL to serve on. defaults to the same as the --out-dir option'
  )
  .option('--no-hmr', 'disable hot module replacement')
  .option('--no-cache', 'disable the filesystem cache')
  .option('--no-source-maps', 'disable sourcemaps')
  .option('--no-autoinstall', 'disable autoinstall')
  .option(
    '-t, --target [target]',
    'set the runtime environment, either "node", "browser" or "electron". defaults to "browser"',
    /^(node|browser|electron)$/
  )
  .option('-V, --version', 'output the version number')
  .action(bundle);

program
  .command('watch [input]')
  .description('starts the bundler in watch mode')
  .option(
    '-d, --out-dir <path>',
    'set the output directory. defaults to "dist"'
  )
  .option(
    '-o, --out-file <filename>',
    'set the output filename for the application entry point.'
  )
  .option(
    '--public-url <url>',
    'set the public URL to serve on. defaults to the same as the --out-dir option'
  )
  .option(
    '--hmr-port <port>',
    'set the port to serve HMR websockets, defaults to random',
    parseInt
  )
  .option(
    '--hmr-hostname <hostname>',
    'set the hostname of HMR websockets, defaults to location.hostname of current window'
  )
  .option('--no-hmr', 'disable hot module replacement')
  .option('--no-cache', 'disable the filesystem cache')
  .option('--no-source-maps', 'disable sourcemaps')
  .option('--no-autoinstall', 'disable autoinstall')
  .option(
    '-t, --target [target]',
    'set the runtime environment, either "node", "browser" or "electron". defaults to "browser"',
    /^(node|browser|electron)$/
  )
  .action(bundle);

program
  .command('build [input]')
  .description('bundles for production')
  .option(
    '-d, --out-dir <path>',
    'set the output directory. defaults to "dist"'
  )
  .option(
    '-o, --out-file <filename>',
    'set the output filename for the application entry point.'
  )
  .option(
    '--public-url <url>',
    'set the public URL to serve on. defaults to the same as the --out-dir option'
  )
  .option('--no-minify', 'disable minification')
  .option('--no-cache', 'disable the filesystem cache')
  .option('--no-source-maps', 'disable sourcemaps')
  .option(
    '-t, --target <target>',
    'set the runtime environment, either "node", "browser" or "electron". defaults to "browser"',
    /^(node|browser|electron)$/
  )
  .option(
    '--detailed-report',
    'print a detailed build report after a completed build'
  )
  .action(bundle);

program
  .command('help [command]')
  .description('display help information for a command')
  .action(function(command) {
    let cmd = program.commands.find(c => c.name() === command) || program;
    cmd.help();
  });

program.on('--help', function() {
  console.log('');
  console.log(
    '  Run `' +
      chalk.bold('parcel help <command>') +
      '` for more information on specific commands'
  );
  console.log('');
});

// Make serve the default command
var args = process.argv;
if (!args[2] || !program.commands.some(c => c.name() === args[2])) {
  args.splice(2, 0, 'serve');
}

program.parse(args);

async function bundle(main, command) {
  // Require bundler here so the help command is fast
  const Bundler = require('../');

  if (command.name() === 'build') {
    process.env.NODE_ENV = 'production';
  } else {
    process.env.NODE_ENV = process.env.NODE_ENV || 'development';
  }

  if (command.cert && command.key) {
    command.https = {
      cert: command.cert,
      key: command.key
    };
  }

  const bundler = new Bundler(main, command);

  if (command.name() === 'serve') {
    const server = await bundler.serve(command.port || 1234, command.https);
    if (command.open) {
      await require('./utils/openInBrowser')(
        `${command.https ? 'https' : 'http'}://localhost:${
          server.address().port
        }`,
        command.open
      );
    }
  } else {
    bundler.bundle();
  }
}


keep keep keep replace keep keep keep keep

const assert = require('assert');
const fs = require('fs');
const {bundle} = require('./utils');
const rimraf = require('rimraf');
const promisify = require('../src/utils/promisify');
const ncp = promisify(require('ncp'));

describe('content hashing', function() {
  beforeEach(function() {
    rimraf.sync(__dirname + '/input');
  });

  it('should update content hash when content changes', async function() {
    await ncp(__dirname + '/integration/html-css', __dirname + '/input');

    await bundle(__dirname + '/input/index.html', {
      production: true
    });

    let html = fs.readFileSync(__dirname + '/dist/index.html', 'utf8');
    let filename = html.match(
      /<link rel="stylesheet" href="[/\\]{1}dist[/\\]{1}(input\.[a-f0-9]+\.css)">/
    )[1];
    assert(fs.existsSync(__dirname + '/dist/' + filename));

    fs.writeFileSync(
      __dirname + '/input/index.css',
      'body { background: green }'
    );

    await bundle(__dirname + '/input/index.html', {
      production: true
    });

    html = fs.readFileSync(__dirname + '/dist/index.html', 'utf8');
    let newFilename = html.match(
      /<link rel="stylesheet" href="[/\\]{1}dist[/\\]{1}(input\.[a-f0-9]+\.css)">/
    )[1];
    assert(fs.existsSync(__dirname + '/dist/' + newFilename));

    assert.notEqual(filename, newFilename);
  });

  it('should update content hash when raw asset changes', async function() {
    await ncp(__dirname + '/integration/import-raw', __dirname + '/input');

    await bundle(__dirname + '/input/index.js', {
      production: true
    });

    let js = fs.readFileSync(__dirname + '/dist/index.js', 'utf8');
    let filename = js.match(/\/dist\/(test\.[0-9a-f]+\.txt)/)[1];
    assert(fs.existsSync(__dirname + '/dist/' + filename));

    fs.writeFileSync(__dirname + '/input/test.txt', 'hello world');

    await bundle(__dirname + '/input/index.js', {
      production: true
    });

    js = fs.readFileSync(__dirname + '/dist/index.js', 'utf8');
    let newFilename = js.match(/\/dist\/(test\.[0-9a-f]+\.txt)/)[1];
    assert(fs.existsSync(__dirname + '/dist/' + newFilename));

    assert.notEqual(filename, newFilename);
  });
});