Skip to content

Commit

Permalink
Merge pull request #3750 from github/koesie10/switch-to-native-fetch
Browse files Browse the repository at this point in the history
Switch from node-fetch to native Node.js fetch
  • Loading branch information
koesie10 authored Oct 9, 2024
2 parents c09077b + f184b21 commit 4e34288
Show file tree
Hide file tree
Showing 22 changed files with 249 additions and 230 deletions.
1 change: 1 addition & 0 deletions extensions/ql-vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [UNRELEASED]

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

## 1.15.0 - 26 September 2024

Expand Down
85 changes: 0 additions & 85 deletions extensions/ql-vscode/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion extensions/ql-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1985,7 +1985,6 @@
"js-yaml": "^4.1.0",
"msw": "^2.2.13",
"nanoid": "^5.0.7",
"node-fetch": "^3.3.2",
"p-queue": "^8.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down
41 changes: 28 additions & 13 deletions extensions/ql-vscode/src/codeql-cli/distribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { reportUnzipProgress } from "../common/vscode/unzip-progress";
import type { Release } from "./distribution/release";
import { ReleasesApiConsumer } from "./distribution/releases-api-consumer";
import { createTimeoutSignal } from "../common/fetch-stream";
import { AbortError } from "node-fetch";

/**
* distribution.ts
Expand Down Expand Up @@ -416,24 +415,40 @@ class ExtensionSpecificDistributionManager {
const totalNumBytes = contentLength
? parseInt(contentLength, 10)
: undefined;
reportStreamProgress(
body,

const reportProgress = reportStreamProgress(
`Downloading CodeQL CLI ${release.name}…`,
totalNumBytes,
progressCallback,
);

body.on("data", onData);

await new Promise((resolve, reject) => {
if (!archiveFile) {
throw new Error("Invariant violation: archiveFile not set");
const reader = body.getReader();
for (;;) {
const { done, value } = await reader.read();
if (done) {
break;
}

body.pipe(archiveFile).on("finish", resolve).on("error", reject);
onData();
reportProgress(value?.length ?? 0);

await new Promise((resolve, reject) => {
archiveFile?.write(value, (err) => {
if (err) {
reject(err);
}
resolve(undefined);
});
});
}

// If an error occurs on the body, we also want to reject the promise (e.g. during a timeout error).
body.on("error", reject);
await new Promise((resolve, reject) => {
archiveFile?.close((err) => {
if (err) {
reject(err);
}
resolve(undefined);
});
});

disposeTimeout();
Expand All @@ -454,8 +469,8 @@ class ExtensionSpecificDistributionManager {
: undefined,
);
} catch (e) {
if (e instanceof AbortError) {
const thrownError = new AbortError("The download timed out.");
if (e instanceof DOMException && e.name === "AbortError") {
const thrownError = new Error("The download timed out.");
thrownError.stack = e.stack;
throw thrownError;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { Response } from "node-fetch";
import { default as fetch } from "node-fetch";
import type { Range } from "semver";
import { compare, parse, satisfies } from "semver";
import { URL } from "url";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { join, resolve } from "path";
import { pathExists } from "fs-extra";
import type { SetupServer } from "msw/node";
import { setupServer } from "msw/node";
import type { UnhandledRequestStrategy } from "msw/lib/core/utils/request/onUnhandledRequest";

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

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

public startServer(): void {
public startServer(
onUnhandledRequest: UnhandledRequestStrategy = "bypass",
): void {
if (this._isListening) {
return;
}

this.server.listen({ onUnhandledRequest: "bypass" });
this.server.listen({ onUnhandledRequest });
this._isListening = true;
}

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

const handlers = await createRequestHandlers(scenarioPath);
this.server.resetHandlers();
this.server.use(...handlers);
this.server.resetHandlers(...handlers);
}

public async saveScenario(
Expand Down
1 change: 0 additions & 1 deletion extensions/ql-vscode/src/common/mock-gh-api/recorder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ensureDir, writeFile } from "fs-extra";
import { join } from "path";

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

import { DisposableObject } from "../disposable-object";
Expand Down
7 changes: 5 additions & 2 deletions extensions/ql-vscode/src/common/octokit.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Octokit } from "@octokit/rest";
import { retry } from "@octokit/plugin-retry";
import fetch from "node-fetch";

export const AppOctokit = Octokit.defaults({
request: {
fetch,
// MSW replaces the global fetch object, so we can't just pass a reference to the
// fetch object at initialization time. Instead, we pass a function that will
// always call the global fetch object.
fetch: (input: string | URL | Request, init?: RequestInit) =>
fetch(input, init),
},
retry,
});
12 changes: 6 additions & 6 deletions extensions/ql-vscode/src/common/vscode/progress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,15 @@ export function withProgress<R>(
* Displays a progress monitor that indicates how much progess has been made
* reading from a stream.
*
* @param readable The stream to read progress from
* @param messagePrefix A prefix for displaying the message
* @param totalNumBytes Total number of bytes in this stream
* @param progress The progress callback used to set messages
*/
export function reportStreamProgress(
readable: NodeJS.ReadableStream,
messagePrefix: string,
totalNumBytes?: number,
progress?: ProgressCallback,
) {
): (bytesRead: number) => void {
if (progress && totalNumBytes) {
let numBytesDownloaded = 0;
const updateProgress = () => {
Expand All @@ -123,15 +121,17 @@ export function reportStreamProgress(
// Display the progress straight away rather than waiting for the first chunk.
updateProgress();

readable.on("data", (data) => {
numBytesDownloaded += data.length;
return (bytesRead: number) => {
numBytesDownloaded += bytesRead;
updateProgress();
});
};
} else if (progress) {
progress({
step: 1,
maxStep: 2,
message: `${messagePrefix} (Size unknown)`,
});
}

return () => {};
}
47 changes: 35 additions & 12 deletions extensions/ql-vscode/src/databases/database-fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { Response } from "node-fetch";
import fetch, { AbortError } from "node-fetch";
import type { InputBoxOptions } from "vscode";
import { Uri, window } from "vscode";
import type { CodeQLCliServer } from "../codeql-cli/cli";
Expand Down Expand Up @@ -536,8 +534,8 @@ export class DatabaseFetcher {
} catch (e) {
disposeTimeout();

if (e instanceof AbortError) {
const thrownError = new AbortError("The request timed out.");
if (e instanceof DOMException && e.name === "AbortError") {
const thrownError = new Error("The request timed out.");
thrownError.stack = e.stack;
throw thrownError;
}
Expand All @@ -556,25 +554,50 @@ export class DatabaseFetcher {
const totalNumBytes = contentLength
? parseInt(contentLength, 10)
: undefined;
reportStreamProgress(body, "Downloading database", totalNumBytes, progress);

body.on("data", onData);
const reportProgress = reportStreamProgress(
"Downloading database",
totalNumBytes,
progress,
);

try {
await new Promise((resolve, reject) => {
body.pipe(archiveFileStream).on("finish", resolve).on("error", reject);
const reader = body.getReader();
for (;;) {
const { done, value } = await reader.read();
if (done) {
break;
}

onData();
reportProgress(value?.length ?? 0);

await new Promise((resolve, reject) => {
archiveFileStream.write(value, (err) => {
if (err) {
reject(err);
}
resolve(undefined);
});
});
}

// If an error occurs on the body, we also want to reject the promise (e.g. during a timeout error).
body.on("error", reject);
await new Promise((resolve, reject) => {
archiveFileStream.close((err) => {
if (err) {
reject(err);
}
resolve(undefined);
});
});
} catch (e) {
// Close and remove the file if an error occurs
archiveFileStream.close(() => {
void remove(archivePath);
});

if (e instanceof AbortError) {
const thrownError = new AbortError("The download timed out.");
if (e instanceof DOMException && e.name === "AbortError") {
const thrownError = new Error("The download timed out.");
thrownError.stack = e.stack;
throw thrownError;
}
Expand Down
Loading

0 comments on commit 4e34288

Please sign in to comment.