1
0
Files
setup-deno/src/version.ts
T

189 lines
5.1 KiB
TypeScript
Raw Normal View History

// @ts-types="@types/semver"
2024-09-13 17:15:41 +02:00
import semver from "semver";
import { fetch } from "undici";
import * as fs from "node:fs";
import * as console from "node:console";
import { setTimeout } from "node:timers";
2021-04-10 02:16:12 +02:00
const GIT_HASH_RE = /^[0-9a-fA-F]{40}$/;
export interface VersionRange {
range: string;
kind: "canary" | "rc" | "stable";
}
export interface Version {
version: string;
kind: "canary" | "rc" | "stable";
}
/** Parses the version from the user into a structure */
export function parseVersionRange(
version: string | undefined,
): VersionRange | null {
2024-10-09 01:54:03 -07:00
version = String(version) || "2.x";
2021-04-10 02:16:12 +02:00
version = version.trim();
2024-09-13 07:41:34 -07:00
if (version === "canary") {
return { range: "latest", kind: "canary" };
2021-04-10 02:16:12 +02:00
}
2024-09-13 07:41:34 -07:00
if (version === "rc") {
return { range: "latest", kind: "rc" };
}
if (version === "latest") {
2024-09-13 17:32:16 +02:00
return { range: "latest", kind: "stable" };
}
2021-04-10 02:16:12 +02:00
if (GIT_HASH_RE.test(version)) {
2024-09-13 07:41:34 -07:00
return { range: version, kind: "canary" };
2021-04-10 02:16:12 +02:00
}
const range = semver.validRange(version);
if (range !== null) {
2024-09-13 17:32:16 +02:00
return { range, kind: "stable" };
2021-04-10 02:16:12 +02:00
}
return null;
}
/** Parses the version from the version file */
export function getDenoVersionFromFile(
versionFilePath: string,
): string | undefined {
if (!fs.existsSync(versionFilePath)) {
throw new Error(
`The specified node version file at: ${versionFilePath} does not exist`,
);
}
const contents = fs.readFileSync(versionFilePath, "utf8");
// .tool-versions typically looks like
// ```
// ruby 2.6.5
// deno 1.43.1
// node 20.0.0
// ```
// This parses the version of Deno from the file
const denoVersionInToolVersions = contents.match(
/^deno\s+v?(?<version>[^\s]+)$/m,
);
return denoVersionInToolVersions?.groups?.version || contents.trim();
}
export function resolveVersion(
{ range, kind }: VersionRange,
): Promise<Version | null> {
2024-09-13 07:41:34 -07:00
if (kind === "canary") {
return resolveCanary(range);
} else if (kind === "rc") {
// range is always "latest"
return resolveReleaseCandidate();
} else {
return resolveRelease(range);
}
}
async function resolveCanary(range: string): Promise<Version | null> {
2024-09-13 07:41:34 -07:00
if (range === "latest") {
const res = await fetchWithRetries(
"https://dl.deno.land/canary-latest.txt",
);
if (res.status !== 200) {
throw new Error(
"Failed to fetch canary version info from dl.deno.land. Please try again later.",
2023-05-30 18:18:39 -04:00
);
2021-04-10 02:16:12 +02:00
}
2024-09-13 07:41:34 -07:00
const version = (await res.text()).trim();
return { version, kind: "canary" };
} else {
return { version: range, kind: "canary" };
}
}
async function resolveReleaseCandidate(): Promise<Version | null> {
2024-09-13 07:41:34 -07:00
const res = await fetchWithRetries(
"https://dl.deno.land/release-rc-latest.txt",
);
if (res.status !== 200) {
throw new Error(
"Failed to fetch release candidate version info from dl.deno.land. Please try again later.",
);
2021-04-10 02:16:12 +02:00
}
2024-09-13 07:41:34 -07:00
const version = semver.clean((await res.text()).trim());
2024-09-13 17:15:41 +02:00
if (version === null) {
throw new Error("Failed to parse release candidate version.");
}
2024-09-13 07:41:34 -07:00
return { version, kind: "rc" };
}
2021-04-10 02:16:12 +02:00
async function resolveRelease(range: string): Promise<Version | null> {
if (range === "latest") {
const res = await fetchWithRetries(
"https://dl.deno.land/release-latest.txt",
);
if (res.status !== 200) {
throw new Error(
"Failed to fetch release version info from dl.deno.land. Please try again later.",
);
}
let version: string | null = (await res.text()).trim();
version = semver.clean(version);
if (version === null) {
2024-09-13 17:15:41 +02:00
throw new Error("Failed to parse release version.");
}
2024-09-13 17:32:16 +02:00
return { version, kind: "stable" };
2024-09-13 07:41:34 -07:00
} else {
const res = await fetchWithRetries("https://deno.com/versions.json");
if (res.status !== 200) {
throw new Error(
"Failed to fetch stable version info from deno.com/versions.json. Please try again later.",
);
}
const versionJson = await res.json();
2024-09-13 17:15:41 +02:00
if (typeof versionJson !== "object" || versionJson === null) {
throw new Error("Fetched stable version info is invalid.");
}
2024-09-13 07:41:34 -07:00
if (!("cli" in versionJson)) {
throw new Error("Fetched stable version info is invalid.");
}
2024-09-13 17:15:41 +02:00
if (!Array.isArray(versionJson.cli)) {
2024-09-13 07:41:34 -07:00
throw new Error("Fetched stable version info is invalid.");
}
const versions: string[] = versionJson.cli;
2024-09-13 07:41:34 -07:00
let version = semver.maxSatisfying(versions, range);
if (version === null) {
return null;
}
version = semver.clean(version);
2024-09-13 17:15:41 +02:00
if (version === null) throw new Error("UNREACHABLE");
2021-04-10 02:16:12 +02:00
2024-09-13 17:32:16 +02:00
return { version, kind: version.includes("-rc.") ? "rc" : "stable" };
2021-04-10 02:16:12 +02:00
}
}
async function fetchWithRetries(url: string, maxRetries = 5) {
2023-05-30 18:18:39 -04:00
let sleepMs = 250;
let iterationCount = 0;
while (true) {
iterationCount++;
try {
const res = await fetch(url);
if (res.status === 200 || iterationCount > maxRetries) {
return res;
}
} catch (err) {
if (iterationCount > maxRetries) {
throw err;
}
}
console.warn(`Failed fetching. Retrying in ${sleepMs}ms...`);
await new Promise((resolve) => setTimeout(resolve, sleepMs));
sleepMs = Math.min(sleepMs * 2, 10_000);
}
}