diff --git a/src/bridge.ts b/src/bridge.ts index 121aaa10..bca594aa 100644 --- a/src/bridge.ts +++ b/src/bridge.ts @@ -94,14 +94,17 @@ class DenoBridge { await fs.mkdir(this.cacheDirectory, { recursive: true }) - const binaryPath = await download(this.cacheDirectory) - const version = await DenoBridge.getBinaryVersion(binaryPath) + const binaryPath = await download(this.cacheDirectory, this.versionRange) + const downloadedVersion = await DenoBridge.getBinaryVersion(binaryPath) - if (version === undefined) { + // We should never get here, because it means that `DENO_VERSION_RANGE` is + // a malformed semver range. If this does happen, let's throw an error so + // that the tests catch it. + if (downloadedVersion === undefined) { throw new Error('Could not read downloaded binary') } - await this.writeVersionFile(version) + await this.writeVersionFile(downloadedVersion) if (this.onAfterDownload) { this.onAfterDownload() diff --git a/src/downloader.ts b/src/downloader.ts index 4fcdedd4..12fa5889 100644 --- a/src/downloader.ts +++ b/src/downloader.ts @@ -3,12 +3,13 @@ import path from 'path' import fetch from 'node-fetch' import StreamZip from 'node-stream-zip' +import semver from 'semver' import { getBinaryExtension, getPlatformTarget } from './platform.js' -const download = async (targetDirectory: string) => { +const download = async (targetDirectory: string, versionRange: string) => { const zipPath = path.join(targetDirectory, 'deno-cli-latest.zip') - const data = await downloadLatestRelease() + const data = await downloadVersion(versionRange) const binaryName = `deno${getBinaryExtension()}` const binaryPath = path.join(targetDirectory, binaryName) const file = fs.createWriteStream(zipPath) @@ -25,8 +26,9 @@ const download = async (targetDirectory: string) => { return binaryPath } -const downloadLatestRelease = async () => { - const url = getReleaseURL() +const downloadVersion = async (versionRange: string) => { + const version = await getLatestVersionForRange(versionRange) + const url = getReleaseURL(version) const res = await fetch(url) if (res.body === null) { @@ -45,10 +47,48 @@ const extractBinaryFromZip = async (zipPath: string, binaryPath: string, binaryN await fs.promises.chmod(binaryPath, '755') } -const getReleaseURL = () => { +const getLatestVersion = async () => { + try { + const response = await fetch('https://dl.deno.land/release-latest.txt') + const data = await response.text() + + // We want to extract from the format `v`. + const version = data.match(/^v?(\d+\.\d+\.\d+)/) + + if (version === null) { + return + } + + return version[1] + } catch { + // This is a no-op. If we failed to retrieve the latest version, let's + // return `undefined` and let the code upstream handle it. + } +} + +const getLatestVersionForRange = async (range: string) => { + const minimumVersion = semver.minVersion(range)?.version + + // We should never get here, because it means that `DENO_VERSION_RANGE` is + // a malformed semver range. If this does happen, let's throw an error so + // that the tests catch it. + if (minimumVersion === undefined) { + throw new Error('Incorrect version range specified by Edge Bundler') + } + + const latestVersion = await getLatestVersion() + + if (latestVersion === undefined || !semver.satisfies(latestVersion, range)) { + return minimumVersion + } + + return latestVersion +} + +const getReleaseURL = (version: string) => { const target = getPlatformTarget() - return `https://github.com/denoland/deno/releases/latest/download/deno-${target}.zip` + return `https://dl.deno.land/release/v${version}/deno-${target}.zip` } export { download } diff --git a/test/main.ts b/test/main.ts index 14a34c40..25a8cafe 100644 --- a/test/main.ts +++ b/test/main.ts @@ -9,7 +9,6 @@ import { DenoBridge } from '../src/bridge.js' test('Downloads the Deno CLI on demand and caches it for subsequent calls', async (t) => { // TODO: Mock HTTP call to Deno source, so that we don't need to download // the actual package as part of the test. - // eslint-disable-next-line no-magic-numbers t.timeout(20_000) const tmpDir = await tmp.dir()