diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 353af06bf7..5c056463e0 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -82,6 +82,15 @@ "integrity": "sha512-nf1LMGZvgFX186geVZR1xMZKKblJiRfiASTHw85zED2kI1yDKHDwTKMdkaCbTlXoRKlGKaDfYywt+V0As30q3w==", "dev": true }, + "@types/node-fetch": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.4.tgz", + "integrity": "sha512-Oz6id++2qAOFuOlE1j0ouk1dzl3mmI1+qINPNBhi9nt/gVOz0G+13Ao6qjhdF0Ys+eOkhu6JnFmt38bR3H0POQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/resolve": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", @@ -91,6 +100,12 @@ "@types/node": "*" } }, + "@types/throttle-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz", + "integrity": "sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==", + "dev": true + }, "@types/vscode": { "version": "1.41.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.41.0.tgz", @@ -536,6 +551,11 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, "nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", @@ -719,6 +739,11 @@ "has-flag": "^3.0.0" } }, + "throttle-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.1.0.tgz", + "integrity": "sha512-AOvyNahXQuU7NN+VVvOOX+uW6FPaWdAOdRP5HfwYxAfCzXTFKRMoIMk+n+po318+ktcChx+F1Dd91G3YHeMKyg==" + }, "tmp": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", diff --git a/editors/code/package.json b/editors/code/package.json index 11d37053eb..8e23718cda 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -25,18 +25,22 @@ }, "dependencies": { "jsonc-parser": "^2.1.0", + "node-fetch": "^2.6.0", + "throttle-debounce": "^2.1.0", "vscode-languageclient": "^6.1.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^11.0.2", "@rollup/plugin-node-resolve": "^7.1.1", "@types/node": "^12.12.25", + "@types/node-fetch": "^2.5.4", + "@types/throttle-debounce": "^2.1.0", "@types/vscode": "^1.41.0", "rollup": "^1.31.0", "tslib": "^1.10.0", "tslint": "^5.20.1", - "typescript-formatter": "^7.2.2", "typescript": "^3.7.5", + "typescript-formatter": "^7.2.2", "vsce": "^1.71.0" }, "activationEvents": [ diff --git a/editors/code/src/github/download_file.ts b/editors/code/src/github/download_file.ts new file mode 100644 index 0000000000..f40750be90 --- /dev/null +++ b/editors/code/src/github/download_file.ts @@ -0,0 +1,26 @@ +import fetch from "node-fetch"; +import { throttle } from "throttle-debounce"; +import * as fs from "fs"; + +export async function downloadFile( + url: string, + destFilePath: fs.PathLike, + onProgress: (readBytes: number, totalBytes: number) => void +): Promise { + onProgress = throttle(100, /* noTrailing: */ true, onProgress); + + const response = await fetch(url); + + const totalBytes = Number(response.headers.get('content-length')); + let readBytes = 0; + + return new Promise((resolve, reject) => response.body + .on("data", (chunk: Buffer) => { + readBytes += chunk.length; + onProgress(readBytes, totalBytes); + }) + .on("end", resolve) + .on("error", reject) + .pipe(fs.createWriteStream(destFilePath)) + ); +} diff --git a/editors/code/src/github/fetch_latest_artifact_metadata.ts b/editors/code/src/github/fetch_latest_artifact_metadata.ts new file mode 100644 index 0000000000..52641ca67d --- /dev/null +++ b/editors/code/src/github/fetch_latest_artifact_metadata.ts @@ -0,0 +1,55 @@ +import fetch from "node-fetch"; + +const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; + +export interface FetchLatestArtifactMetadataOpts { + repoName: string; + repoOwner: string; + artifactFileName: string; +} + +export interface ArtifactMetadata { + releaseName: string; + releaseDate: Date; + downloadUrl: string; +} + +export async function fetchLatestArtifactMetadata( + opts: FetchLatestArtifactMetadataOpts +): Promise { + + const repoOwner = encodeURIComponent(opts.repoOwner); + const repoName = encodeURIComponent(opts.repoName); + + const apiEndpointPath = `/repos/${repoOwner}/${repoName}/releases/latest`; + const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath; + + // We skip runtime type checks for simplicity (here we cast from `any` to `Release`) + + const response: GithubRelease = await fetch(requestUrl, { + headers: { Accept: "application/vnd.github.v3+json" } + }) + .then(res => res.json()); + + const artifact = response.assets.find(artifact => artifact.name === opts.artifactFileName); + + return !artifact ? null : { + releaseName: response.name, + releaseDate: new Date(response.published_at), + downloadUrl: artifact.browser_download_url + }; + + // Noise denotes tremendous amount of data that we are not using here + interface GithubRelease { + name: string; + published_at: Date; + assets: Array<{ + browser_download_url: string; + + [noise: string]: unknown; + }>; + + [noise: string]: unknown; + } + +}