Related discussion: https://github.com/angular/angular/pull/23576#discussion_r187925949. PR Close #25671
		
			
				
	
	
		
			84 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			84 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import * as fs from 'fs';
 | 
						|
import fetch from 'node-fetch';
 | 
						|
import {dirname} from 'path';
 | 
						|
import {mkdir} from 'shelljs';
 | 
						|
import {promisify} from 'util';
 | 
						|
import {CircleCiApi} from '../common/circle-ci-api';
 | 
						|
import {assert, assertNotMissingOrEmpty, computeArtifactDownloadPath, Logger} from '../common/utils';
 | 
						|
import {PreviewServerError} from './preview-error';
 | 
						|
 | 
						|
export interface GithubInfo {
 | 
						|
  org: string;
 | 
						|
  pr: number;
 | 
						|
  repo: string;
 | 
						|
  sha: string;
 | 
						|
  success: boolean;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * A helper that can get information about builds and download build artifacts.
 | 
						|
 */
 | 
						|
export class BuildRetriever {
 | 
						|
  private logger = new Logger('BuildRetriever');
 | 
						|
  constructor(private api: CircleCiApi, private downloadSizeLimit: number, private downloadDir: string) {
 | 
						|
    assert(downloadSizeLimit > 0, 'Invalid parameter "downloadSizeLimit" should be a number greater than 0.');
 | 
						|
    assertNotMissingOrEmpty('downloadDir', downloadDir);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Get GitHub information about a build
 | 
						|
   * @param buildNum The number of the build for which to retrieve the info.
 | 
						|
   * @returns The Github org, repo, PR and latest SHA for the specified build.
 | 
						|
   */
 | 
						|
  public async getGithubInfo(buildNum: number): Promise<GithubInfo> {
 | 
						|
    const buildInfo = await this.api.getBuildInfo(buildNum);
 | 
						|
    const githubInfo: GithubInfo = {
 | 
						|
      org: buildInfo.username,
 | 
						|
      pr: getPrFromBranch(buildInfo.branch),
 | 
						|
      repo: buildInfo.reponame,
 | 
						|
      sha: buildInfo.vcs_revision,
 | 
						|
      success: !buildInfo.failed,
 | 
						|
    };
 | 
						|
    return githubInfo;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Make a request to the given URL for a build artifact and store it locally.
 | 
						|
   * @param buildNum the number of the CircleCI build whose artifact we want to download.
 | 
						|
   * @param pr the number of the PR that triggered the CircleCI build.
 | 
						|
   * @param sha the commit in the PR that triggered the CircleCI build.
 | 
						|
   * @param artifactPath the path on CircleCI where the artifact was stored.
 | 
						|
   * @returns A promise to the file path where the downloaded file was stored.
 | 
						|
   */
 | 
						|
  public async downloadBuildArtifact(buildNum: number, pr: number, sha: string, artifactPath: string): Promise<string> {
 | 
						|
    try {
 | 
						|
      const outPath = computeArtifactDownloadPath(this.downloadDir, pr, sha, artifactPath);
 | 
						|
      const downloadExists = await new Promise(resolve => fs.exists(outPath, exists => resolve(exists)));
 | 
						|
      if (!downloadExists) {
 | 
						|
        const url = await this.api.getBuildArtifactUrl(buildNum, artifactPath);
 | 
						|
        const response = await fetch(url, {size: this.downloadSizeLimit});
 | 
						|
        if (response.status !== 200) {
 | 
						|
          throw new PreviewServerError(response.status, `Error ${response.status} - ${response.statusText}`);
 | 
						|
        }
 | 
						|
        const buffer = await response.buffer();
 | 
						|
        mkdir('-p', dirname(outPath));
 | 
						|
        await promisify(fs.writeFile)(outPath, buffer);
 | 
						|
      }
 | 
						|
      return outPath;
 | 
						|
    } catch (error) {
 | 
						|
      this.logger.warn(error);
 | 
						|
      const status = (error.type === 'max-size') ? 413 : 500;
 | 
						|
      throw new PreviewServerError(status, `CircleCI artifact download failed (${error.message || error})`);
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function getPrFromBranch(branch: string): number {
 | 
						|
  // CircleCI only exposes PR numbers via the `branch` field :-(
 | 
						|
  const match = /^pull\/(\d+)$/.exec(branch);
 | 
						|
  if (!match) {
 | 
						|
    throw new Error(`No PR found in branch field: ${branch}`);
 | 
						|
  }
 | 
						|
  return +match[1];
 | 
						|
}
 |