Path: blob/main/extensions/copilot/script/build/downloadBinary.ts
13389 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/4import * as crypto from 'crypto';5import * as fs from 'fs';6import * as https from 'https';7import * as path from 'path';8import * as tar from 'tar';9import * as zlib from 'zlib';1011const REPO_ROOT = path.join(__dirname, '..', '..');1213export interface IBinary {14url: string;15sha256: string;16destination: string;17}1819export async function ensureBinary(binary: IBinary) {20const binaryPath = path.join(REPO_ROOT, binary.destination);21if (fs.existsSync(binaryPath)) {22const sha256 = await computeSha256(binaryPath);23if (sha256 === binary.sha256) {24console.log(`Binary ${binary.destination} already exists and matches expected checksum.`);25return;26}27console.log(`Binary ${binary.destination} already exists but does not match expected checksum. \n - Expected: ${binary.sha256}\n - Actual: ${sha256}\nRe-downloading...`);28}2930console.log(`Downloading binary ${binary.destination}...`);31await fs.promises.mkdir(path.dirname(binaryPath), { recursive: true });32const tempPath = path.join(path.dirname(binaryPath), crypto.randomUUID() + '.tgz');33try {34await downloadFile(binary.url, tempPath);35await untar(tempPath, path.dirname(binaryPath), /*strip*/2);36const sha256 = await computeSha256(binaryPath);37if (sha256 !== binary.sha256) {38throw new Error(`Downloaded binary ${binary.destination} does not match expected checksum. Expected: ${binary.sha256}, actual: ${sha256}.`);39}40} finally {41await fs.promises.unlink(tempPath);42}43}4445export function computeSha256(filePath: string): Promise<string> {46return new Promise((resolve, reject) => {47const hash = crypto.createHash('sha256');48const stream = fs.createReadStream(filePath);49stream.on('error', reject);50stream.on('data', (chunk) => hash.update(chunk));51stream.on('end', () => resolve(hash.digest('hex')));52});53}5455export function downloadFile(url: string, tempPath: string, headers?: Record<string, string>): Promise<void> {56return new Promise((resolve, reject) => {57https.get(url, { headers }, (response) => {58if (response.headers.location) {59console.log(`Following redirect to ${response.headers.location}`);60return downloadFile(response.headers.location, tempPath).then(resolve, reject);61}6263if (response.statusCode === 404) {64return reject(new Error(`File not found: ${url}`));65}6667const file = fs.createWriteStream(tempPath);68response.pipe(file);69file.on('finish', () => {70file.close();71resolve();72});7374}).on('error', (err) => {75fs.unlink(tempPath, () => reject(err));76});77});78}7980export function get(url: string, opts: https.RequestOptions): Promise<string> {81return new Promise((resolve, reject) => {82let result = '';83https.get(url, opts, response => {84if (response.headers.location) {85console.log(`Following redirect to ${response.headers.location}`);86get(response.headers.location, opts).then(resolve, reject);87}8889if (response.statusCode !== 200) {90reject(new Error('Request failed: ' + response.statusCode));91}9293response.on('data', d => {94result += d.toString();95});9697response.on('end', () => {98resolve(result);99});100101response.on('error', e => {102reject(e);103});104});105});106}107108export function untar(filePath: string, destination: string, strip?: number): Promise<void> {109return new Promise((resolve, reject) => {110const readStream = fs.createReadStream(filePath);111const writeStream = zlib.createGunzip();112const extractStream = tar.extract({113cwd: destination,114strip,115strict: true,116onentry: (entry: any) => {117console.log(`Extracting ${entry.path}`);118}119});120121readStream.on('error', reject);122writeStream.on('error', reject);123extractStream.on('error', reject);124125extractStream.on('end', () => {126resolve();127});128129readStream.pipe(writeStream).pipe(extractStream);130});131}132133134