Skip to content
Snippets Groups Projects
update.ts 5.65 KiB
Newer Older
import { createHash } from 'crypto';
import { coerce } from 'semver';
import { parseUrlPath } from './extract';
import { skip, isSpace, removeComments } from './util';
import got from '../../util/got';
import { logger } from '../../logger';
import { Upgrade } from '../common';

// TODO: Refactor
export async function updateDependency(
  content: string,
  upgrade: Upgrade
): Promise<string> {
  logger.trace('updateDependency()');
  /*
    1. Update url field
    2. Update sha256 field
   */
  let newContent = content;
  let newUrl: string;
  let file: string;
  // Example urls:
  // "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
  // "https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz"
Rhys Arkins's avatar
Rhys Arkins committed
  const oldParsedUrlPath = parseUrlPath(upgrade.managerData.url);
  if (!oldParsedUrlPath) {
    logger.debug(
Rhys Arkins's avatar
Rhys Arkins committed
      `Failed to update - upgrade.managerData.url is invalid ${upgrade.depName}`
    );
    return content;
  }
  try {
    newUrl = `https://github.com/${upgrade.managerData.ownerName}/${
      upgrade.managerData.repoName
    }/releases/download/${upgrade.newValue}/${
      upgrade.managerData.repoName
    }-${coerce(upgrade.newValue)}.tar.gz`;
    file = (await got(newUrl, {
      encoding: null,
    })).body;
  } catch (errOuter) {
    logger.debug(
      `Failed to download release download for ${upgrade.depName} - trying archive instead`
      newUrl = `https://github.com/${upgrade.managerData.ownerName}/${upgrade.managerData.repoName}/archive/${upgrade.newValue}.tar.gz`;
      file = (await got(newUrl, { encoding: null })).body;
    } catch (errInner) {
      logger.debug(
        `Failed to download archive download for ${upgrade.depName} - update failed`
      );
      return content;
    }
  }
  const newParsedUrlPath = parseUrlPath(newUrl);
  if (!newParsedUrlPath) {
    logger.debug(`Failed to update url for dependency ${upgrade.depName}`);
    return content;
  }
  if (upgrade.newValue !== newParsedUrlPath.currentValue) {
    logger.debug(`Failed to update url for dependency ${upgrade.depName}`);
    return content;
  }
  let newSha256;
  try {
    newSha256 = createHash('sha256')
      .update(file)
      .digest('hex');
  } catch (err) /* istanbul ignore next */ {
    logger.warn({ err }, 'Failed to generate new sha256 for homebrew');
  }
Rhys Arkins's avatar
Rhys Arkins committed
  newContent = updateUrl(content, upgrade.managerData.url, newUrl);
  if (!newContent) {
    logger.debug(`Failed to update url for dependency ${upgrade.depName}`);
    return content;
  }
Rhys Arkins's avatar
Rhys Arkins committed
  newContent = updateSha256(newContent, upgrade.managerData.sha256, newSha256);
  if (!newContent) {
    logger.debug(`Failed to update sha256 for dependency ${upgrade.depName}`);
    return content;
  }
  return newContent;
}

function updateUrl(
  content: string,
  oldUrl: string,
  newUrl: string
): string | null {
  const urlRegExp = /(^|\s)url(\s)/;
  let i = content.search(urlRegExp);
  if (i === -1) {
    return null;
  }
  if (isSpace(content[i])) {
    i += 1;
  }
  let newContent = replaceUrl(i, content, oldUrl, newUrl);
  const testContent = getUrlTestContent(content, oldUrl, newUrl);
  if (!newContent || !testContent) {
    return null;
  }
  while (removeComments(newContent) !== testContent) {
    i += 'url'.length;
    i += content.substring(i).search(urlRegExp);
    if (isSpace(content[i])) {
      i += 1;
    }
    newContent = replaceUrl(i, content, oldUrl, newUrl);
  }
  return newContent;
}

function getUrlTestContent(
  content: string,
  oldUrl: string,
  newUrl: string
): string {
  const urlRegExp = /(^|\s)url(\s)/;
  const cleanContent = removeComments(content);
  let j = cleanContent.search(urlRegExp);
  if (isSpace(cleanContent[j])) {
    j += 1;
  }
  const testContent = replaceUrl(j, cleanContent, oldUrl, newUrl);
  return testContent;
}

function replaceUrl(
  idx: number,
  content: string,
  oldUrl: string,
  newUrl: string
): string | null {
  let i = idx;
  i += 'url'.length;
  i = skip(i, content, c => isSpace(c));
  const chr = content[i];
  if (chr !== '"' && chr !== "'") {
    return null;
  }
  i += 1;
  const newContent =
    content.substring(0, i) + content.substring(i).replace(oldUrl, newUrl);
  return newContent;
}

function updateSha256(
  content: string,
  oldSha256: string,
  newSha256: string
): string | null {
  const sha256RegExp = /(^|\s)sha256(\s)/;
  let i = content.search(sha256RegExp);
  if (i === -1) {
    return null;
  }
  if (isSpace(content[i])) {
    i += 1;
  }
  let newContent = replaceSha256(i, content, oldSha256, newSha256);
  const testContent = getSha256TestContent(content, oldSha256, newSha256);
  if (!newContent || !testContent) {
    return null;
  }
  while (removeComments(newContent) !== testContent) {
    i += 'sha256'.length;
    i += content.substring(i).search(sha256RegExp);
    if (isSpace(content[i])) {
      i += 1;
    }
    newContent = replaceSha256(i, content, oldSha256, newSha256);
  }
  return newContent;
}

function getSha256TestContent(
  content: string,
  oldSha256: string,
  newSha256: string
): string | null {
  const sha256RegExp = /(^|\s)sha256(\s)/;
  const cleanContent = removeComments(content);
  let j = cleanContent.search(sha256RegExp);
  if (isSpace(cleanContent[j])) {
    j += 1;
  }
  const testContent = replaceSha256(j, cleanContent, oldSha256, newSha256);
  return testContent;
}

function replaceSha256(
  idx: number,
  content: string,
  oldSha256: string,
  newSha256: string
): string | null {
  let i = idx;
  i += 'sha256'.length;
  i = skip(i, content, c => isSpace(c));
  const chr = content[i];
  if (chr !== '"' && chr !== "'") {
    return null;
  }
  i += 1;
  const newContent =
    content.substring(0, i) +
    content.substring(i).replace(oldSha256, newSha256);
  return newContent;
}