index.js
function travisStatus(options, callback) {
  if (!callback && typeof options === 'function') {
    callback = options;
    options = null;
  }

  if (callback && typeof callback !== 'function') {
    throw new TypeError('callback must be a function');
  }

  let agent, gitChecker, travisChecker;
  try {
    if (options && typeof options !== 'object') {
      throw new TypeError('options must be an object');
    }
    options = options || {};

    if (options.repo) {
      GitStatusChecker.checkSlugFormat(options.repo);
    }
    if (options.storeRepo) {
      GitStatusChecker.checkSlugFormat(options.storeRepo);
    }

    // If the caller didn't request an agent behavior, control it ourselves.
    // Each function call will use HTTP keep-alive for the duration of the
    // function, but not after completion, which callers may not expect.
    let {requestOpts} = options;
    if (!requestOpts
        || (requestOpts.agent === undefined
         && requestOpts.agentClass === undefined
         && requestOpts.agentOptions === undefined
         && requestOpts.forever === undefined
         && requestOpts.pool === undefined)) {
      const apiUrl =
        url.parse(options.apiEndpoint || TravisStatusChecker.ORG_URI);
      const Agent = apiUrl.protocol === 'https:' ? https.Agent
        : apiUrl.protocol === 'http:' ? http.Agent
          : null;
      if (Agent) {
        agent = new Agent({keepAlive: true});
        // .destroy() and keepAlive added to Agent in 0.11.4, nodejs@9fc9b874
        // If Agent doesn't support keepAlive/destroy, we don't need/want it.
        if (typeof agent.destroy === 'function') {
          requestOpts = Object.assign({}, requestOpts);
          requestOpts.agent = agent;
          options = Object.assign({}, options);
          options.requestOpts = requestOpts;
        } else {
          agent = undefined;
        }
      }
    }

    gitChecker = new GitStatusChecker(options);
    travisChecker = new TravisStatusChecker(options);
  } catch (errOptions) {
    const errResult = Promise.reject(errOptions);
    return nodeify(errResult, callback);
  }

  let repoSlugP;
  if (options.storeRepo) {
    const storedSlugP = gitChecker.tryStoreSlug(options.storeRepo);
    // If both .repo and .storeRepo are present, store .storeRepo and use .repo
    repoSlugP =
      options.repo ? storedSlugP.then(() => options.repo)
        : storedSlugP;
  } else if (options.repo) {
    repoSlugP = Promise.resolve(options.repo);
  } else {
    const foundSlugP = gitChecker.findSlug()
      .then(GitStatusChecker.checkSlugFormat);
    if (options.interactive) {
      repoSlugP = foundSlugP.then((slug) => gitChecker.tryStoreSlug(slug));
    } else {
      repoSlugP = foundSlugP;
    }
  }

  let localCommitP;
  if (options.commit) {
    localCommitP = gitChecker.resolveHash(options.commit)
      .then((resolved) => {
        const localCommit = {sha: resolved};
        if (resolved !== options.commit) {
          localCommit.name = options.commit;
        }
        return localCommit;
      });
  }

  // Before doing remote queries, ensure that there are no errors locally
  const slugForQueryP = Promise.all([repoSlugP, localCommitP])
    .then((slugAndHash) => slugAndHash[0]);

  let resultP;
  if (options.branch) {
    const branchP = options.branch === true ? gitChecker.detectBranch()
      : Promise.resolve(options.branch);
    resultP = Promise.all([slugForQueryP, branchP])
      .then((results) => {
        const slug = results[0];
        const branch = results[1];
        return travisChecker.getBranch(slug, branch, options);
      });
  } else {
    const repoP =
      slugForQueryP.then((slug) => travisChecker.getRepo(slug, options));

    if (localCommitP) {
      // Add build information to result
      resultP = repoP.then((repo) => travisChecker.getBuild(
        repo.repo.slug,
        repo.repo.last_build_id
      )
        .then((build) => Object.assign({}, repo, build)));
    } else {
      resultP = repoP;
    }
  }

  let checkedResultP = resultP;
  if (localCommitP) {
    checkedResultP = Promise.all([resultP, localCommitP])
      .then((all) => {
        const result = all[0];
        const localCommit = all[1];
        checkBuildCommit(result, localCommit);
        return result;
      });
  }

  let cleanupP;
  if (agent) {
    cleanupP = checkedResultP.then(
      (result) => { agent.destroy(); return result; },
      (err) => { agent.destroy(); return Promise.reject(err); }
    );
  } else {
    cleanupP = checkedResultP;
  }

  return nodeify(cleanupP, callback);
}
function travisStatus(options, callback) {
  if (!callback && typeof options === 'function') {
    callback = options;
    options = null;
  }

  if (callback && typeof callback !== 'function') {
    throw new TypeError('callback must be a function');
  }

  let agent, gitChecker, travisChecker;
  try {
    if (options && typeof options !== 'object') {
      throw new TypeError('options must be an object');
    }
    options = options || {};

    if (options.repo) {
      GitStatusChecker.checkSlugFormat(options.repo);
    }
    if (options.storeRepo) {
      GitStatusChecker.checkSlugFormat(options.storeRepo);
    }

    // If the caller didn't request an agent behavior, control it ourselves.
    // Each function call will use HTTP keep-alive for the duration of the
    // function, but not after completion, which callers may not expect.
    let {requestOpts} = options;
    if (!requestOpts
        || (requestOpts.agent === undefined
         && requestOpts.agentClass === undefined
         && requestOpts.agentOptions === undefined
         && requestOpts.forever === undefined
         && requestOpts.pool === undefined)) {
      const apiUrl =
        url.parse(options.apiEndpoint || TravisStatusChecker.ORG_URI);
      const Agent = apiUrl.protocol === 'https:' ? https.Agent
        : apiUrl.protocol === 'http:' ? http.Agent
          : null;
      if (Agent) {
        agent = new Agent({keepAlive: true});
        // .destroy() and keepAlive added to Agent in 0.11.4, nodejs@9fc9b874
        // If Agent doesn't support keepAlive/destroy, we don't need/want it.
        if (typeof agent.destroy === 'function') {
          requestOpts = Object.assign({}, requestOpts);
          requestOpts.agent = agent;
          options = Object.assign({}, options);
          options.requestOpts = requestOpts;
        } else {
          agent = undefined;
        }
      }
    }

    gitChecker = new GitStatusChecker(options);
    travisChecker = new TravisStatusChecker(options);
  } catch (errOptions) {
    const errResult = Promise.reject(errOptions);
    return nodeify(errResult, callback);
  }

  let repoSlugP;
  if (options.storeRepo) {
    const storedSlugP = gitChecker.tryStoreSlug(options.storeRepo);
    // If both .repo and .storeRepo are present, store .storeRepo and use .repo
    repoSlugP =
      options.repo ? storedSlugP.then(() => options.repo)
        : storedSlugP;
  } else if (options.repo) {
    repoSlugP = Promise.resolve(options.repo);
  } else {
    const foundSlugP = gitChecker.findSlug()
      .then(GitStatusChecker.checkSlugFormat);
    if (options.interactive) {
      repoSlugP = foundSlugP.then((slug) => gitChecker.tryStoreSlug(slug));
    } else {
      repoSlugP = foundSlugP;
    }
  }

  let localCommitP;
  if (options.commit) {
    localCommitP = gitChecker.resolveHash(options.commit)
      .then((resolved) => {
        const localCommit = {sha: resolved};
        if (resolved !== options.commit) {
          localCommit.name = options.commit;
        }
        return localCommit;
      });
  }

  // Before doing remote queries, ensure that there are no errors locally
  const slugForQueryP = Promise.all([repoSlugP, localCommitP])
    .then((slugAndHash) => slugAndHash[0]);

  let resultP;
  if (options.branch) {
    const branchP = options.branch === true ? gitChecker.detectBranch()
      : Promise.resolve(options.branch);
    resultP = Promise.all([slugForQueryP, branchP])
      .then((results) => {
        const slug = results[0];
        const branch = results[1];
        return travisChecker.getBranch(slug, branch, options);
      });
  } else {
    const repoP =
      slugForQueryP.then((slug) => travisChecker.getRepo(slug, options));

    if (localCommitP) {
      // Add build information to result
      resultP = repoP.then((repo) => travisChecker.getBuild(
        repo.repo.slug,
        repo.repo.last_build_id
      )
        .then((build) => Object.assign({}, repo, build)));
    } else {
      resultP = repoP;
    }
  }

  let checkedResultP = resultP;
  if (localCommitP) {
    checkedResultP = Promise.all([resultP, localCommitP])
      .then((all) => {
        const result = all[0];
        const localCommit = all[1];
        checkBuildCommit(result, localCommit);
        return result;
      });
  }

  let cleanupP;
  if (agent) {
    cleanupP = checkedResultP.then(
      (result) => { agent.destroy(); return result; },
      (err) => { agent.destroy(); return Promise.reject(err); }
    );
  } else {
    cleanupP = checkedResultP;
  }

  return nodeify(cleanupP, callback);
}
Options for {@link travisStatus}.

@typedef {{
apiEndpoint: string|undefined,
branch: string|boolean|undefined,
commit: string|undefined,
err: stream.Writable|undefined,
in: stream.Readable|undefined,
out: stream.Writable|undefined,
repo: string|undefined,
requestOpts: Object|undefined,
storeRepo: string|undefined,
token: string|undefined,
wait: number|undefined
}} TravisStatusOptions
@property {boolean=} interactive behave as if being run interactively
@property {string=} apiEndpoint Travis API server to talk to
@property {(string|boolean)=} branch query latest build for named branch,
or the current branch
@property {string=} commit require build to be for a specific commit
@property {stream.Writable=} err Stream to which errors (and non-output
status messages) are written. (default: <code>process.stderr</code>)
@property {stream.Readable=} in Stream from which input is read. (default:
<code>process.stdin</code>)
@property {stream.Writable=} out Stream to which output is written.
(default: <code>process.stdout</code>)
@property {string=} repo repository to use (default: will try to detect from
current git clone)
@property {Object=} requestOpts Options for Travis CI API requests (suitable
for the {@link https://www.npmjs.com/package/request request module}).
Callers are encouraged to pass the <code>agent</code> or
<code>forever</code> options to leverage TCP keep-alive across requests.
@property {string=} storeRepo repository value (as described for
<code>repo</code>) to store permanently for future use.  Is used for this
invocation if <code>repo</code> is not set.
@property {string=} token access token to use
@property {number=} wait wait if build is pending (timeout in milliseconds)
 var TravisStatusOptions; Gets the current Travis CI status of a repo/branch.

@param {?TravisStatusOptions=} options Options.
@param {?function(Error, Object=)=} callback Callback function called
with the current build information from the Travis CI API, or an
<code>Error</code> if it could not be retrieved.
@return {!Promise<!Object>|undefined} If <code>callback</code> is not given,
a <code>Promise</code> with the current build information from the Travis CI
API, or <code>Error</code> if it could not be retrieved.
Otherwise <code>undefined</code>.