Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix update-node-version script for non-existent @types/node version #3788

Merged
merged 3 commits into from
Nov 7, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/update-node-version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Update Node version
working-directory: extensions/ql-vscode
run: |
npx ts-node scripts/update-node-version.ts
npx vite-node scripts/update-node-version.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the benefit of using vite-node over ts-node?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vite-node has better support for mixed ESM/CJS modules, so I switched all other scripts to use vite-node in 85abd9a. I noticed I forgot to update this use, so this just makes it consistent with the other scripts which are defined in package.json.

shell: bash
- name: Get current Node version
working-directory: extensions/ql-vscode
Expand Down
94 changes: 75 additions & 19 deletions extensions/ql-vscode/scripts/update-node-version.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
import { join, resolve } from "path";
import { execSync } from "child_process";
import { outputFile, readFile, readJSON } from "fs-extra";
import { outputFile, readJSON } from "fs-extra";
import { getVersionInformation } from "./util/vscode-versions";
import { fetchJson } from "./util/fetch";
import { SemVer } from "semver";

const extensionDirectory = resolve(__dirname, "..");

interface Release {
tag_name: string;
}

interface NpmViewError {
error: {
code: string;
summary: string;
detail: string;
};
}

interface ExecError extends Error {
status: number;
stdout: string;
}

function isExecError(e: unknown): e is ExecError {
return (
e instanceof Error &&
"status" in e &&
typeof e.status === "number" &&
"stdout" in e &&
typeof e.stdout === "string"
);
}

async function updateNodeVersion() {
const latestVsCodeRelease = await fetchJson<Release>(
"https://api.github.com/repos/microsoft/vscode/releases/latest",
Expand All @@ -23,19 +47,7 @@ async function updateNodeVersion() {
`VS Code ${versionInformation.vscodeVersion} uses Electron ${versionInformation.electronVersion} and Node ${versionInformation.nodeVersion}`,
);

let currentNodeVersion = (
await readFile(join(extensionDirectory, ".nvmrc"), "utf8")
).trim();
if (currentNodeVersion.startsWith("v")) {
currentNodeVersion = currentNodeVersion.slice(1);
}

if (currentNodeVersion === versionInformation.nodeVersion) {
console.log("Node version is already up to date");
return;
}

console.log("Node version needs to be updated, updating now");
console.log("Updating files related to the Node version");

await outputFile(
join(extensionDirectory, ".nvmrc"),
Expand All @@ -49,20 +61,64 @@ async function updateNodeVersion() {
"utf8",
);

const nodeVersion = new SemVer(versionInformation.nodeVersion);

// The @types/node version needs to match the first two parts of the Node
// version, e.g. if the Node version is 18.17.3, the @types/node version
// should be 18.17.*. This corresponds with the documentation at
// https://github.com/definitelytyped/definitelytyped#how-do-definitely-typed-package-versions-relate-to-versions-of-the-corresponding-library
// "The patch version of the type declaration package is unrelated to the library patch version. This allows
// Definitely Typed to safely update type declarations for the same major/minor version of a library."
// 18.17.* is equivalent to >=18.17.0 <18.18.0
const typesNodeVersion = versionInformation.nodeVersion
.split(".")
.slice(0, 2)
.join(".");
// In some cases, the @types/node version matching the exact Node version may not exist, in which case we'll try
// the next lower minor version, and so on, until we find a version that exists.
const typesNodeSemver = new SemVer(nodeVersion);
typesNodeSemver.patch = 0;

// eslint-disable-next-line no-constant-condition
while (true) {
const typesNodeVersion = `${typesNodeSemver.major}.${typesNodeSemver.minor}.*`;

try {
// Check that this version actually exists
console.log(`Checking if @types/node@${typesNodeVersion} exists`);

execSync(`npm view --json "@types/node@${typesNodeVersion}"`, {
encoding: "utf-8",
stdio: "pipe",
});

console.log(`@types/node@${typesNodeVersion} exists`);

// If it exists, we can break out of this loop
break;
} catch (e: unknown) {
if (!isExecError(e)) {
throw e;
}

const error = JSON.parse(e.stdout) as NpmViewError;
if (error.error.code !== "E404") {
throw new Error(error.error.detail);
}

console.log(
`@types/node package doesn't exist for ${typesNodeVersion}, trying a lower version (${error.error.summary})`,
);

// This means the version doesn't exist, so we'll try decrementing the minor version
typesNodeSemver.minor -= 1;
if (typesNodeSemver.minor < 0) {
throw new Error(
`Could not find a suitable @types/node version for Node ${nodeVersion.format()}`,
);
}
}
}

packageJson.engines.node = `^${versionInformation.nodeVersion}`;
packageJson.devDependencies["@types/node"] = `${typesNodeVersion}.*`;
packageJson.devDependencies["@types/node"] =
`${typesNodeSemver.major}.${typesNodeSemver.minor}.*`;

await outputFile(
join(extensionDirectory, "package.json"),
Expand Down
Loading