@vscode/test-electron
Advanced tools
Comparing version 2.2.0 to 2.2.1
# Changelog | ||
### 2.1.4 | 2022-06-27 | ||
### 2.2.1 | 2022-12-06 | ||
- Add an idle `timeout` for downloads | ||
### 2.1.5 | 2022-06-27 | ||
- Automatically retry if VS Code download fails | ||
@@ -6,0 +10,0 @@ |
@@ -15,2 +15,3 @@ import { ProgressReporter } from './progress'; | ||
readonly extractSync?: boolean; | ||
readonly timeout?: number; | ||
} | ||
@@ -17,0 +18,0 @@ export declare const defaultCachePath: string; |
@@ -23,4 +23,4 @@ "use strict"; | ||
const DOWNLOAD_ATTEMPTS = 3; | ||
async function fetchLatestStableVersion() { | ||
const versions = await request.getJSON(vscodeStableReleasesAPI); | ||
async function fetchLatestStableVersion(timeout) { | ||
const versions = await request.getJSON(vscodeStableReleasesAPI, timeout); | ||
if (!versions || !Array.isArray(versions) || !versions[0]) { | ||
@@ -31,11 +31,11 @@ throw Error('Failed to get latest VS Code version'); | ||
} | ||
async function isValidVersion(version, platform) { | ||
async function isValidVersion(version, platform, timeout) { | ||
if (version === 'insiders') { | ||
return true; | ||
} | ||
const stableVersionNumbers = await request.getJSON(vscodeStableReleasesAPI); | ||
const stableVersionNumbers = await request.getJSON(vscodeStableReleasesAPI, timeout); | ||
if (stableVersionNumbers.includes(version)) { | ||
return true; | ||
} | ||
const insiderCommits = await request.getJSON(vscodeInsiderCommitsAPI(platform)); | ||
const insiderCommits = await request.getJSON(vscodeInsiderCommitsAPI(platform), timeout); | ||
if (insiderCommits.includes(version)) { | ||
@@ -57,5 +57,6 @@ return true; | ||
} | ||
const timeout = options.timeout; | ||
const downloadUrl = util_2.getVSCodeDownloadUrl(options.version, options.platform); | ||
(_a = options.reporter) === null || _a === void 0 ? void 0 : _a.report({ stage: progress_1.ProgressReportStage.ResolvingCDNLocation, url: downloadUrl }); | ||
const res = await request.getStream(downloadUrl); | ||
const res = await request.getStream(downloadUrl, timeout); | ||
if (res.statusCode !== 302) { | ||
@@ -69,6 +70,7 @@ throw 'Failed to get VS Code archive location'; | ||
res.destroy(); | ||
const download = await request.getStream(url); | ||
const download = await request.getStream(url, timeout); | ||
const totalBytes = Number(download.headers['content-length']); | ||
const contentType = download.headers['content-type']; | ||
const isZip = contentType ? contentType === 'application/zip' : url.endsWith('.zip'); | ||
const timeoutCtrl = new request.TimeoutController(timeout); | ||
(_b = options.reporter) === null || _b === void 0 ? void 0 : _b.report({ stage: progress_1.ProgressReportStage.Downloading, url, bytesSoFar: 0, totalBytes }); | ||
@@ -79,2 +81,3 @@ let bytesSoFar = 0; | ||
bytesSoFar += chunk.length; | ||
timeoutCtrl.touch(); | ||
(_a = options.reporter) === null || _a === void 0 ? void 0 : _a.report({ stage: progress_1.ProgressReportStage.Downloading, url, bytesSoFar, totalBytes }); | ||
@@ -84,4 +87,9 @@ }); | ||
var _a; | ||
timeoutCtrl.dispose(); | ||
(_a = options.reporter) === null || _a === void 0 ? void 0 : _a.report({ stage: progress_1.ProgressReportStage.Downloading, url, bytesSoFar: totalBytes, totalBytes }); | ||
}); | ||
timeoutCtrl.signal.addEventListener('abort', () => { | ||
download.emit('error', new request.TimeoutError(timeout)); | ||
download.destroy(); | ||
}); | ||
return { stream: download, format: isZip ? 'zip' : 'tgz' }; | ||
@@ -117,2 +125,3 @@ } | ||
await new Promise((resolve, reject) => stream | ||
.on('error', reject) | ||
.pipe(unzipper_1.Extract({ path: extractDir })) | ||
@@ -142,7 +151,10 @@ .on('close', resolve) | ||
function spawnDecompressorChild(command, args, input) { | ||
const child = cp.spawn(command, args, { stdio: 'pipe' }); | ||
input === null || input === void 0 ? void 0 : input.pipe(child.stdin); | ||
child.stderr.pipe(process.stderr); | ||
child.stdout.pipe(process.stdout); | ||
return new Promise((resolve, reject) => { | ||
const child = cp.spawn(command, args, { stdio: 'pipe' }); | ||
if (input) { | ||
input.on('error', reject); | ||
input.pipe(child.stdin); | ||
} | ||
child.stderr.pipe(process.stderr); | ||
child.stdout.pipe(process.stdout); | ||
child.on('error', reject); | ||
@@ -159,6 +171,6 @@ child.on('exit', code => code === 0 ? resolve() : reject(new Error(`Failed to unzip archive, exited with ${code}`))); | ||
let version = options === null || options === void 0 ? void 0 : options.version; | ||
const { platform = util_2.systemDefaultPlatform, cachePath = exports.defaultCachePath, reporter = new progress_1.ConsoleReporter(process.stdout.isTTY), extractSync = false, } = options; | ||
const { platform = util_2.systemDefaultPlatform, cachePath = exports.defaultCachePath, reporter = new progress_1.ConsoleReporter(process.stdout.isTTY), extractSync = false, timeout = 15000, } = options; | ||
if (version) { | ||
if (version === 'stable') { | ||
version = await fetchLatestStableVersion(); | ||
version = await fetchLatestStableVersion(timeout); | ||
} | ||
@@ -170,3 +182,3 @@ else { | ||
if (!fs.existsSync(path.resolve(cachePath, `vscode-${platform}-${version}`))) { | ||
if (!(await isValidVersion(version, platform))) { | ||
if (!(await isValidVersion(version, platform, timeout))) { | ||
throw Error(`Invalid version ${version}`); | ||
@@ -178,3 +190,3 @@ } | ||
else { | ||
version = await fetchLatestStableVersion(); | ||
version = await fetchLatestStableVersion(timeout); | ||
} | ||
@@ -221,3 +233,3 @@ reporter.report({ stage: progress_1.ProgressReportStage.ResolvedVersion, version }); | ||
try { | ||
const { stream, format } = await downloadVSCodeArchive({ version, platform, cachePath, reporter }); | ||
const { stream, format } = await downloadVSCodeArchive({ version, platform, cachePath, reporter, timeout }); | ||
await unzipVSCode(reporter, downloadedPath, extractSync, stream, format); | ||
@@ -224,0 +236,0 @@ reporter.report({ stage: progress_1.ProgressReportStage.NewInstallComplete, downloadedPath }); |
/// <reference types="node" /> | ||
import { IncomingMessage } from 'http'; | ||
export declare function getStream(api: string): Promise<IncomingMessage>; | ||
export declare function getJSON<T>(api: string): Promise<T>; | ||
export declare function getStream(api: string, timeout: number): Promise<IncomingMessage>; | ||
export declare function getJSON<T>(api: string, timeout: number): Promise<T>; | ||
export declare class TimeoutController { | ||
private readonly timeout; | ||
private handle; | ||
private readonly ctrl; | ||
get signal(): AbortSignal; | ||
constructor(timeout: number); | ||
touch(): void; | ||
dispose(): void; | ||
private readonly reject; | ||
} | ||
export declare class TimeoutError extends Error { | ||
constructor(duration: number); | ||
} |
@@ -7,14 +7,25 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getJSON = exports.getStream = void 0; | ||
exports.TimeoutError = exports.TimeoutController = exports.getJSON = exports.getStream = void 0; | ||
const https = require("https"); | ||
const util_1 = require("./util"); | ||
async function getStream(api) { | ||
async function getStream(api, timeout) { | ||
const ctrl = new TimeoutController(timeout); | ||
return new Promise((resolve, reject) => { | ||
https.get(api, util_1.urlToOptions(api), res => resolve(res)).on('error', reject); | ||
}); | ||
ctrl.signal.addEventListener('abort', () => { | ||
reject(new TimeoutError(timeout)); | ||
req.destroy(); | ||
}); | ||
const req = https.get(api, util_1.urlToOptions(api), (res) => resolve(res)).on('error', reject); | ||
}).finally(() => ctrl.dispose()); | ||
} | ||
exports.getStream = getStream; | ||
async function getJSON(api) { | ||
async function getJSON(api, timeout) { | ||
const ctrl = new TimeoutController(timeout); | ||
return new Promise((resolve, reject) => { | ||
https.get(api, util_1.urlToOptions(api), res => { | ||
ctrl.signal.addEventListener('abort', () => { | ||
reject(new TimeoutError(timeout)); | ||
req.destroy(); | ||
}); | ||
const req = https | ||
.get(api, util_1.urlToOptions(api), (res) => { | ||
if (res.statusCode !== 200) { | ||
@@ -24,6 +35,8 @@ reject('Failed to get JSON'); | ||
let data = ''; | ||
res.on('data', chunk => { | ||
res.on('data', (chunk) => { | ||
ctrl.touch(); | ||
data += chunk; | ||
}); | ||
res.on('end', () => { | ||
ctrl.dispose(); | ||
try { | ||
@@ -39,5 +52,33 @@ const jsonData = JSON.parse(data); | ||
res.on('error', reject); | ||
}).on('error', reject); | ||
}); | ||
}) | ||
.on('error', reject); | ||
}).finally(() => ctrl.dispose()); | ||
} | ||
exports.getJSON = getJSON; | ||
class TimeoutController { | ||
constructor(timeout) { | ||
this.timeout = timeout; | ||
this.ctrl = new AbortController(); | ||
this.reject = () => { | ||
this.ctrl.abort(); | ||
}; | ||
this.handle = setTimeout(this.reject, timeout); | ||
} | ||
get signal() { | ||
return this.ctrl.signal; | ||
} | ||
touch() { | ||
clearTimeout(this.handle); | ||
this.handle = setTimeout(this.reject, this.timeout); | ||
} | ||
dispose() { | ||
clearTimeout(this.handle); | ||
} | ||
} | ||
exports.TimeoutController = TimeoutController; | ||
class TimeoutError extends Error { | ||
constructor(duration) { | ||
super(`@vscode/test-electron request timeout out after ${duration}ms`); | ||
} | ||
} | ||
exports.TimeoutError = TimeoutError; |
@@ -92,2 +92,8 @@ import { DownloadVersion, DownloadPlatform } from './download'; | ||
extractSync?: boolean; | ||
/** | ||
* Number of milliseconds after which to time out if no data is received from | ||
* the remote when downloading VS Code. Note that this is an 'idle' timeout | ||
* and does not enforce the total time VS Code may take to download. | ||
*/ | ||
timeout?: number; | ||
} | ||
@@ -94,0 +100,0 @@ /** |
@@ -116,3 +116,3 @@ "use strict"; | ||
const remoteUrl = `https://update.code.visualstudio.com/api/update/${platform}/insider/latest`; | ||
return await request.getJSON(remoteUrl); | ||
return await request.getJSON(remoteUrl, 30000); | ||
} | ||
@@ -119,0 +119,0 @@ exports.getLatestInsidersMetadata = getLatestInsidersMetadata; |
{ | ||
"name": "@vscode/test-electron", | ||
"version": "2.2.0", | ||
"version": "2.2.1", | ||
"scripts": { | ||
@@ -12,3 +12,3 @@ "compile": "tsc -p ./", | ||
"engines": { | ||
"node": ">=8.9.3" | ||
"node": ">=16" | ||
}, | ||
@@ -22,3 +22,3 @@ "dependencies": { | ||
"devDependencies": { | ||
"@types/node": "^12", | ||
"@types/node": "^18", | ||
"@types/rimraf": "^3.0.0", | ||
@@ -25,0 +25,0 @@ "@types/unzipper": "^0.10.3", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
66765
1115
6