Popular
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272import { colors, ensureDir, extname, fromFileUrl, join, normalize, resolve, toFileUrl,} from "./deps.ts";import { ArchRecord, CacheLocation, FetchOptions, NestedCrossRecord, OsRecord,} from "./types.ts";import { cacheDir, denoCacheDir, isFile, urlToFilename } from "./util.ts";
export const defaultExtensions: OsRecord<string> = { darwin: "dylib", linux: "so", windows: "dll",};
export const defaultPrefixes: OsRecord<string> = { darwin: "lib", linux: "lib", windows: "",};
function getCrossOption<T>( record?: NestedCrossRecord<T>,): T | undefined { if (record === undefined) { return; }
if ( "darwin" in record || "linux" in record || "windows" in record ) { const subrecord = record[Deno.build.os];
if ( subrecord && typeof subrecord === "object" && ("x86_64" in subrecord || "aarch64" in subrecord) ) { return (subrecord as ArchRecord<T>)[Deno.build.arch]; } else { return subrecord as T; } }
if ( "x86_64" in record || "aarch64" in record ) { const subrecord = record[Deno.build.arch];
if ( subrecord && typeof subrecord === "object" && ("darwin" in subrecord || "linux" in subrecord || "windows" in subrecord) ) { return (subrecord as OsRecord<T>)[Deno.build.os]; } else { return subrecord as T; } }}
/** * Creates a cross-platform url for the specified options * * @param options See {@link FetchOptions} * @returns A fully specified url to the specified file */export function createDownloadURL(options: FetchOptions): URL { if (typeof options === "string" || options instanceof URL) { options = { url: options }; }
// Initialize default options options.extensions ??= defaultExtensions; options.prefixes ??= defaultPrefixes;
// Clean extensions to not contain a leading dot for (const key in options.extensions) { const os = key as typeof Deno.build.os; if (options.extensions[os] !== undefined) { options.extensions[os] = options.extensions[os].replace(/\.?(.+)/, "$1"); } }
// Get the os-specific url let url: URL; if (options.url instanceof URL) { url = options.url; } else if (typeof options.url === "string") { url = options.url.startsWith("file://") ? new URL(options.url) : toFileUrl(resolve(options.url)); } else { const tmpUrl = getCrossOption(options.url); if (tmpUrl === undefined) { throw new TypeError( `An URL for the "${Deno.build.os}-${Deno.build.arch}" target was not provided.`, ); }
if (typeof tmpUrl === "string") { url = tmpUrl.startsWith("file://") ? new URL(tmpUrl) : toFileUrl(resolve(tmpUrl)); } else { url = tmpUrl; } }
// Assemble automatic cross-platform named urls here if ( "name" in options && !Object.values(options.extensions).includes(extname(url.pathname)) ) { if (!url.pathname.endsWith("/")) { url.pathname = `${url.pathname}/`; }
const prefix = getCrossOption(options.prefixes) ?? ""; const suffix = getCrossOption(options.suffixes) ?? ""; const extension = options.extensions[Deno.build.os];
if (options.name === undefined) { throw new TypeError( `Expected the "name" property for an automatically assembled URL.`, ); }
const filename = `${prefix}${options.name}${suffix}.${extension}`;
url = new URL(filename, url); }
return url;}
/** * Return the path to the cache location along with ensuring its existance * * @param location See the {@link CacheLocation} type * @returns The cache location path */export async function ensureCacheLocation( location: CacheLocation = "deno",): Promise<string> { if (location === "deno") { const dir = denoCacheDir();
if (dir === undefined) { throw new Error( "Could not get the deno cache directory, try using another CacheLocation in the plug options.", ); }
location = join(dir, "plug"); } else if (location === "cache") { const dir = cacheDir();
if (dir === undefined) { throw new Error( "Could not get the cache directory, try using another CacheLocation in the plug options.", ); }
location = join(dir, "plug"); } else if (location === "cwd") { location = join(Deno.cwd(), "plug"); } else if (location === "tmp") { location = await Deno.makeTempDir({ prefix: "plug" }); } else if (typeof location === "string" && location.startsWith("file://")) { location = fromFileUrl(location); } else if (location instanceof URL) { if (location?.protocol !== "file:") { throw new TypeError( "Cannot use any other protocol than file:// for an URL cache location.", ); }
location = fromFileUrl(location); }
location = resolve(normalize(location));
await ensureDir(location);
return location;}
/** * Downloads a file using the specified {@link FetchOptions} * * @param options See {@link FetchOptions} * @returns The path to the downloaded file in its cached location */export async function download(options: FetchOptions): Promise<string> { const location = (typeof options === "object" && "location" in options ? options.location : undefined) ?? "deno"; const setting = (typeof options === "object" && "cache" in options ? options.cache : undefined) ?? "use"; const [url, directory] = await Promise.all([ createDownloadURL(options), ensureCacheLocation(location), ]); const cacheBasePath = join(directory, await urlToFilename(url)); const cacheFilePath = `${cacheBasePath}${extname(url.pathname)}`; const cacheMetaPath = `${cacheBasePath}.metadata.json`; const cached = setting === "use" ? await isFile(cacheFilePath) : setting === "only" || setting !== "reloadAll";
if (!cached) { const meta = { url }; switch (url.protocol) { case "http:": case "https:": { console.log(`${colors.green("Downloading")} ${url}`); const response = await fetch(url.toString());
if (!response.ok) { if (response.status === 404) { throw new Error(`Could not find ${url}`); } else { throw new Deno.errors.Http( `${response.status} ${response.statusText}`, ); } }
const file = await Deno.create(cacheFilePath); await response.body!.pipeTo(file.writable); file.close();
break; }
case "file:": { console.log(`${colors.green("Copying")} ${url}`); await Deno.copyFile(fromFileUrl(url), cacheFilePath); break; }
default: { throw new TypeError( `Cannot fetch to cache using the "${url.protocol}" protocol`, ); } } await Deno.writeTextFile(cacheMetaPath, JSON.stringify(meta)); }
if (!await isFile(cacheFilePath)) { throw new Error(`Could not find "${url}" in cache.`); }
return cacheFilePath;}