Skip to content

Commit 4e34288

Browse files
authored
Merge pull request #3750 from github/koesie10/switch-to-native-fetch
Switch from node-fetch to native Node.js fetch
2 parents c09077b + f184b21 commit 4e34288

File tree

22 files changed

+249
-230
lines changed

22 files changed

+249
-230
lines changed

extensions/ql-vscode/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [UNRELEASED]
44

55
- Increase the required version of VS Code to 1.90.0. [#3737](https://github.com/github/vscode-codeql/pull/3737)
6+
- Fix a bug where some variant analysis results failed to download. [#3750](https://github.com/github/vscode-codeql/pull/3750)
67

78
## 1.15.0 - 26 September 2024
89

extensions/ql-vscode/package-lock.json

Lines changed: 0 additions & 85 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extensions/ql-vscode/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1985,7 +1985,6 @@
19851985
"js-yaml": "^4.1.0",
19861986
"msw": "^2.2.13",
19871987
"nanoid": "^5.0.7",
1988-
"node-fetch": "^3.3.2",
19891988
"p-queue": "^8.0.1",
19901989
"react": "^18.3.1",
19911990
"react-dom": "^18.3.1",

extensions/ql-vscode/src/codeql-cli/distribution.ts

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import { reportUnzipProgress } from "../common/vscode/unzip-progress";
2828
import type { Release } from "./distribution/release";
2929
import { ReleasesApiConsumer } from "./distribution/releases-api-consumer";
3030
import { createTimeoutSignal } from "../common/fetch-stream";
31-
import { AbortError } from "node-fetch";
3231

3332
/**
3433
* distribution.ts
@@ -416,24 +415,40 @@ class ExtensionSpecificDistributionManager {
416415
const totalNumBytes = contentLength
417416
? parseInt(contentLength, 10)
418417
: undefined;
419-
reportStreamProgress(
420-
body,
418+
419+
const reportProgress = reportStreamProgress(
421420
`Downloading CodeQL CLI ${release.name}…`,
422421
totalNumBytes,
423422
progressCallback,
424423
);
425424

426-
body.on("data", onData);
427-
428-
await new Promise((resolve, reject) => {
429-
if (!archiveFile) {
430-
throw new Error("Invariant violation: archiveFile not set");
425+
const reader = body.getReader();
426+
for (;;) {
427+
const { done, value } = await reader.read();
428+
if (done) {
429+
break;
431430
}
432431

433-
body.pipe(archiveFile).on("finish", resolve).on("error", reject);
432+
onData();
433+
reportProgress(value?.length ?? 0);
434+
435+
await new Promise((resolve, reject) => {
436+
archiveFile?.write(value, (err) => {
437+
if (err) {
438+
reject(err);
439+
}
440+
resolve(undefined);
441+
});
442+
});
443+
}
434444

435-
// If an error occurs on the body, we also want to reject the promise (e.g. during a timeout error).
436-
body.on("error", reject);
445+
await new Promise((resolve, reject) => {
446+
archiveFile?.close((err) => {
447+
if (err) {
448+
reject(err);
449+
}
450+
resolve(undefined);
451+
});
437452
});
438453

439454
disposeTimeout();
@@ -454,8 +469,8 @@ class ExtensionSpecificDistributionManager {
454469
: undefined,
455470
);
456471
} catch (e) {
457-
if (e instanceof AbortError) {
458-
const thrownError = new AbortError("The download timed out.");
472+
if (e instanceof DOMException && e.name === "AbortError") {
473+
const thrownError = new Error("The download timed out.");
459474
thrownError.stack = e.stack;
460475
throw thrownError;
461476
}

extensions/ql-vscode/src/codeql-cli/distribution/releases-api-consumer.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import type { Response } from "node-fetch";
2-
import { default as fetch } from "node-fetch";
31
import type { Range } from "semver";
42
import { compare, parse, satisfies } from "semver";
53
import { URL } from "url";

extensions/ql-vscode/src/common/mock-gh-api/mock-gh-api-server.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { join, resolve } from "path";
22
import { pathExists } from "fs-extra";
33
import type { SetupServer } from "msw/node";
44
import { setupServer } from "msw/node";
5+
import type { UnhandledRequestStrategy } from "msw/lib/core/utils/request/onUnhandledRequest";
56

67
import { DisposableObject } from "../disposable-object";
78

@@ -26,12 +27,14 @@ export class MockGitHubApiServer extends DisposableObject {
2627
this.recorder = this.push(new Recorder(this.server));
2728
}
2829

29-
public startServer(): void {
30+
public startServer(
31+
onUnhandledRequest: UnhandledRequestStrategy = "bypass",
32+
): void {
3033
if (this._isListening) {
3134
return;
3235
}
3336

34-
this.server.listen({ onUnhandledRequest: "bypass" });
37+
this.server.listen({ onUnhandledRequest });
3538
this._isListening = true;
3639
}
3740

@@ -54,8 +57,7 @@ export class MockGitHubApiServer extends DisposableObject {
5457
const scenarioPath = join(scenariosPath, scenarioName);
5558

5659
const handlers = await createRequestHandlers(scenarioPath);
57-
this.server.resetHandlers();
58-
this.server.use(...handlers);
60+
this.server.resetHandlers(...handlers);
5961
}
6062

6163
public async saveScenario(

extensions/ql-vscode/src/common/mock-gh-api/recorder.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { ensureDir, writeFile } from "fs-extra";
22
import { join } from "path";
33

4-
import fetch from "node-fetch";
54
import type { SetupServer } from "msw/node";
65

76
import { DisposableObject } from "../disposable-object";
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { Octokit } from "@octokit/rest";
22
import { retry } from "@octokit/plugin-retry";
3-
import fetch from "node-fetch";
43

54
export const AppOctokit = Octokit.defaults({
65
request: {
7-
fetch,
6+
// MSW replaces the global fetch object, so we can't just pass a reference to the
7+
// fetch object at initialization time. Instead, we pass a function that will
8+
// always call the global fetch object.
9+
fetch: (input: string | URL | Request, init?: RequestInit) =>
10+
fetch(input, init),
811
},
912
retry,
1013
});

extensions/ql-vscode/src/common/vscode/progress.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,15 @@ export function withProgress<R>(
9797
* Displays a progress monitor that indicates how much progess has been made
9898
* reading from a stream.
9999
*
100-
* @param readable The stream to read progress from
101100
* @param messagePrefix A prefix for displaying the message
102101
* @param totalNumBytes Total number of bytes in this stream
103102
* @param progress The progress callback used to set messages
104103
*/
105104
export function reportStreamProgress(
106-
readable: NodeJS.ReadableStream,
107105
messagePrefix: string,
108106
totalNumBytes?: number,
109107
progress?: ProgressCallback,
110-
) {
108+
): (bytesRead: number) => void {
111109
if (progress && totalNumBytes) {
112110
let numBytesDownloaded = 0;
113111
const updateProgress = () => {
@@ -123,15 +121,17 @@ export function reportStreamProgress(
123121
// Display the progress straight away rather than waiting for the first chunk.
124122
updateProgress();
125123

126-
readable.on("data", (data) => {
127-
numBytesDownloaded += data.length;
124+
return (bytesRead: number) => {
125+
numBytesDownloaded += bytesRead;
128126
updateProgress();
129-
});
127+
};
130128
} else if (progress) {
131129
progress({
132130
step: 1,
133131
maxStep: 2,
134132
message: `${messagePrefix} (Size unknown)`,
135133
});
136134
}
135+
136+
return () => {};
137137
}

extensions/ql-vscode/src/databases/database-fetcher.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import type { Response } from "node-fetch";
2-
import fetch, { AbortError } from "node-fetch";
31
import type { InputBoxOptions } from "vscode";
42
import { Uri, window } from "vscode";
53
import type { CodeQLCliServer } from "../codeql-cli/cli";
@@ -536,8 +534,8 @@ export class DatabaseFetcher {
536534
} catch (e) {
537535
disposeTimeout();
538536

539-
if (e instanceof AbortError) {
540-
const thrownError = new AbortError("The request timed out.");
537+
if (e instanceof DOMException && e.name === "AbortError") {
538+
const thrownError = new Error("The request timed out.");
541539
thrownError.stack = e.stack;
542540
throw thrownError;
543541
}
@@ -556,25 +554,50 @@ export class DatabaseFetcher {
556554
const totalNumBytes = contentLength
557555
? parseInt(contentLength, 10)
558556
: undefined;
559-
reportStreamProgress(body, "Downloading database", totalNumBytes, progress);
560557

561-
body.on("data", onData);
558+
const reportProgress = reportStreamProgress(
559+
"Downloading database",
560+
totalNumBytes,
561+
progress,
562+
);
562563

563564
try {
564-
await new Promise((resolve, reject) => {
565-
body.pipe(archiveFileStream).on("finish", resolve).on("error", reject);
565+
const reader = body.getReader();
566+
for (;;) {
567+
const { done, value } = await reader.read();
568+
if (done) {
569+
break;
570+
}
571+
572+
onData();
573+
reportProgress(value?.length ?? 0);
574+
575+
await new Promise((resolve, reject) => {
576+
archiveFileStream.write(value, (err) => {
577+
if (err) {
578+
reject(err);
579+
}
580+
resolve(undefined);
581+
});
582+
});
583+
}
566584

567-
// If an error occurs on the body, we also want to reject the promise (e.g. during a timeout error).
568-
body.on("error", reject);
585+
await new Promise((resolve, reject) => {
586+
archiveFileStream.close((err) => {
587+
if (err) {
588+
reject(err);
589+
}
590+
resolve(undefined);
591+
});
569592
});
570593
} catch (e) {
571594
// Close and remove the file if an error occurs
572595
archiveFileStream.close(() => {
573596
void remove(archivePath);
574597
});
575598

576-
if (e instanceof AbortError) {
577-
const thrownError = new AbortError("The download timed out.");
599+
if (e instanceof DOMException && e.name === "AbortError") {
600+
const thrownError = new Error("The download timed out.");
578601
thrownError.stack = e.stack;
579602
throw thrownError;
580603
}

0 commit comments

Comments
 (0)