Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion extensions/ql-vscode/scripts/util/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
export async function fetchJson<T>(url: string): Promise<T> {
async function fetchResponse(url: string): Promise<Response> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(
`Could not fetch ${url}: ${response.status} ${response.statusText}`,
);
}

return response;
}

export async function fetchJson<T>(url: string): Promise<T> {
const response = await fetchResponse(url);
return (await response.json()) as T;
}

export async function fetchText(url: string): Promise<string> {
const response = await fetchResponse(url);
return await response.text();
}
50 changes: 39 additions & 11 deletions extensions/ql-vscode/scripts/util/vscode-versions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { minVersion } from "semver";
import { fetchJson } from "./fetch";
import { minVersion, valid } from "semver";
import { fetchJson, fetchText } from "./fetch";

type VsCodePackageJson = {
devDependencies: {
electron: string;
devDependencies?: {
electron?: string;
};
};

Expand All @@ -15,6 +15,40 @@
);
}

async function getVsCodeNpmrc(version: string): Promise<string> {
return await fetchText(
`https://raw.githubusercontent.com/microsoft/vscode/${version}/.npmrc`,
);
}

export function parseElectronVersion(npmrc: string): string {
const electronVersion = /^target="([^"]+)"$/m.exec(npmrc)?.[1];
if (!electronVersion || !valid(electronVersion)) {
Comment thread
cklin marked this conversation as resolved.
Dismissed
throw new Error(
"Could not find a valid Electron version in VS Code .npmrc",
);
}

return electronVersion;
}

async function getVsCodeElectronVersion(version: string): Promise<string> {
const packageJson = await getVsCodePackageJson(version);
const packageElectronVersion = packageJson.devDependencies?.electron;
if (packageElectronVersion) {
const electronVersion = minVersion(packageElectronVersion)?.version;
Comment thread
cklin marked this conversation as resolved.
Dismissed
if (!electronVersion) {
throw new Error(
"Could not find a valid Electron version in VS Code package.json",
);
}

return electronVersion;
}

return parseElectronVersion(await getVsCodeNpmrc(version));
}

interface ElectronVersion {
version: string;
date: string;
Expand Down Expand Up @@ -44,13 +78,7 @@
export async function getVersionInformation(
vscodeVersion: string,
): Promise<VersionInformation> {
const vsCodePackageJson = await getVsCodePackageJson(vscodeVersion);
const electronVersion = minVersion(
vsCodePackageJson.devDependencies.electron,
)?.version;
if (!electronVersion) {
throw new Error("Could not find Electron version");
}
const electronVersion = await getVsCodeElectronVersion(vscodeVersion);

const electronReleases = await getElectronReleases();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
getVersionInformation,
parseElectronVersion,
} from "../../../../scripts/util/vscode-versions";

const electronReleases = [
{
version: "29.4.0",
node: "20.9.0",
chrome: "122.0.6261.156",
},
{
version: "42.10.0",
node: "24.18.1",
chrome: "148.0.7778.280",
},
];

function mockFetch(
responses: Record<string, unknown>,
): jest.SpiedFunction<typeof fetch> {
return jest.spyOn(globalThis, "fetch").mockImplementation(async (input) => {
const url = input.toString();
const response = responses[url];
if (response === undefined) {
throw new Error(`Unexpected URL: ${url}`);
}

return {
ok: true,
json: async () => response,
text: async () => response,
} as Response;
});
}

afterEach(() => {
jest.restoreAllMocks();
});

describe("parseElectronVersion", () => {
it("parses the Electron target from a VS Code .npmrc", () => {
expect(
parseElectronVersion(
['target="42.10.0"', 'runtime="electron"', "disturl=example"].join(
"\n",
),
),
).toBe("42.10.0");
});

it.each([
["a missing target", 'runtime="electron"'],
["an invalid target", 'target="not-a-version"'],
["an unquoted target", "target=42.10.0"],
])("rejects %s", (_description, npmrc) => {
expect(() => parseElectronVersion(npmrc)).toThrow(
"Could not find a valid Electron version in VS Code .npmrc",
);
});
});

describe("getVersionInformation", () => {
it("reads Electron from package.json for older VS Code releases", async () => {
const fetchMock = mockFetch({
"https://raw.githubusercontent.com/microsoft/vscode/1.90.0/package.json":
{
devDependencies: {
electron: "29.4.0",
},
},
"https://releases.electronjs.org/releases.json": electronReleases,
});

await expect(getVersionInformation("1.90.0")).resolves.toEqual({
vscodeVersion: "1.90.0",
electronVersion: "29.4.0",
nodeVersion: "20.9.0",
chromiumVersion: "122.0.6261.156",
});
expect(fetchMock).not.toHaveBeenCalledWith(
"https://raw.githubusercontent.com/microsoft/vscode/1.90.0/.npmrc",
);
});

it("falls back to .npmrc for newer VS Code releases", async () => {
mockFetch({
"https://raw.githubusercontent.com/microsoft/vscode/1.137.0/package.json":
{
devDependencies: {},
},
"https://raw.githubusercontent.com/microsoft/vscode/1.137.0/.npmrc":
'target="42.10.0"\nruntime="electron"\n',
"https://releases.electronjs.org/releases.json": electronReleases,
});

await expect(getVersionInformation("1.137.0")).resolves.toEqual({
vscodeVersion: "1.137.0",
electronVersion: "42.10.0",
nodeVersion: "24.18.1",
chromiumVersion: "148.0.7778.280",
});
});
});