UNPKG

gitly

Version:

An API to download and/or extract git repositories

341 lines (326 loc) 10.3 kB
// src/utils/clone.ts import spawn from "cross-spawn"; import { rm } from "fs/promises"; import path from "path"; import * as tar2 from "tar"; // src/utils/archive.ts import os from "os"; import { join } from "path"; import * as tar from "tar"; function getArchiveUrl(info, options = {}) { var _a6; const { path: repo, type } = info; if ((_a6 = options.url) == null ? void 0 : _a6.filter) { return options.url.filter(info); } switch (info.hostname) { case "bitbucket": return `https://bitbucket.org${repo}/get/${type}.tar.gz`; case "gitlab": return `https://gitlab.com${repo}/-/archive/${type}/${repo.split("/")[2]}-${type}.tar.gz`; default: return `https://github.com${repo}/archive/${type}.tar.gz`; } } function getArchivePath(info, options = {}) { const { path: path2, type, hostname: site } = info; return join( options.temp || join(os.homedir(), ".gitly"), site, path2, `${type}.tar.gz` ); } var extract2 = tar.extract; // src/utils/error.ts var GitlyAbstractError = class extends Error { constructor(message, code = -1) { super(message); this.message = message; this.code = code; this.rawMessage = message; const type = this.type = this.ctor.type; this.message = `[${type ? `gitly:${type}` : "gitly"}]: ${message}`; Object.setPrototypeOf(this, new.target.prototype); } get ctor() { return this.constructor; } }; var _a; var GitlyUknownError = (_a = class extends GitlyAbstractError { }, _a.type = "unknown" /* Unknown */, _a); var _a2; var GitlyFetchError = (_a2 = class extends GitlyAbstractError { }, _a2.type = "fetch" /* Fetch */, _a2); var _a3; var GitlyExtractError = (_a3 = class extends GitlyAbstractError { }, _a3.type = "extract" /* Extract */, _a3); var _a4; var GitlyDownloadError = (_a4 = class extends GitlyAbstractError { }, _a4.type = "download" /* Download */, _a4); var _a5; var GitlyCloneError = (_a5 = class extends GitlyAbstractError { }, _a5.type = "clone" /* Clone */, _a5); // src/utils/execute.ts async function execute(tasks) { return new Promise((resolve2, reject) => { const next = () => execute(tasks.slice(1)).then(resolve2); return tasks[0]().then((t) => t ? resolve2(t) : next()).catch(reject); }); } // src/utils/exists.ts import { constants, promises as fs } from "fs"; import { isAbsolute } from "path"; // src/utils/parse.ts import { URL } from "url"; function parse(url, options = {}) { const { url: normalized, host } = normalizeURL(url, options); const result = new URL(normalized); const paths = (result.pathname || "").split("/").filter(Boolean); const owner = paths.shift() || ""; const repository = paths.shift() || ""; return { protocol: (result.protocol || "https").replace(/:/g, ""), host: result.host || host || "github.com", hostname: (result.hostname || host || "github").replace(/\.(\S+)/, ""), hash: result.hash || "", href: result.href || "", path: result.pathname || "", repository, owner, type: (result.hash || "#master").substring(1) }; } function normalizeURL(url, options) { const { host } = options; if (url.includes("0") && Array.from(url.matchAll(/0/g)).length > 25) { throw new Error("Invalid argument"); } if ((host == null ? void 0 : host.includes("0")) && Array.from(host.matchAll(/0/g)).length > 25) { throw new Error("Invalid argument"); } const isNotProtocol = !/http(s)?:\/\//.test(url); const hasHost = /([\S]+):.+/.test(url); const hasTLD = /[\S]+\.([\D]+)/.test(url); let normalizedURL = url.replace("www.", "").replace(".git", ""); let updatedHost = host || ""; if (isNotProtocol && hasHost) { const hostMatch = url.match(/([\S]+):.+/); updatedHost = hostMatch ? hostMatch[1] : ""; normalizedURL = `https://${updatedHost}.com/${normalizedURL.replace(`${updatedHost}:`, "")}`; } else if (isNotProtocol && hasTLD) { normalizedURL = `https://${normalizedURL}`; } else if (isNotProtocol) { const tldMatch = (host || "").match(/[\S]+\.([\D]+)/); const domain = (host || "github").replace( `.${tldMatch ? tldMatch[1] : "com"}`, "" ); const tld = tldMatch ? tldMatch[1] : "com"; normalizedURL = `https://${domain}.${tld}/${normalizedURL}`; } return { url: normalizedURL, host: updatedHost }; } // src/utils/exists.ts async function exists(path2, options = {}) { let _path = path2; if (!isAbsolute(path2)) { _path = getArchivePath(parse(path2), options); } try { await fs.access(_path, constants.F_OK); return true; } catch (_) { } return false; } // src/utils/offline.ts import { promises as dns } from "dns"; var { lookup } = dns; async function isOffline() { try { await lookup("google.com"); return false; } catch (_) { } return true; } // src/utils/clone.ts async function clone(repository, options = {}) { const info = parse(repository, options); const archivePath = getArchivePath(info, options); const directory = archivePath.replace(/\.tar\.gz$/, ""); let order = []; const local = async () => exists(`${archivePath}.tar.gz`); const remote = async () => { var _a6; if (await exists(archivePath)) { await rm(archivePath); } const depth = ((_a6 = options == null ? void 0 : options.git) == null ? void 0 : _a6.depth) || 1; if (repository.includes("--upload-pack") || directory.includes("--upload-pack")) { throw new GitlyCloneError("Invalid argument"); } if (typeof depth !== "number") { throw new GitlyCloneError("Invalid depth option"); } if (info.href.includes("--upload-pack")) { throw new GitlyCloneError("Invalid argument"); } const child = spawn("git", [ "clone", "--depth", depth.toString(), info.href, directory ]); await new Promise((resolve2, reject) => { child.on("error", (reason) => reject(new GitlyCloneError(reason.message))); child.on("close", (code) => { if (code === 0) { rm(path.resolve(directory, ".git"), { recursive: true }).then( () => ( // Create the archive after cloning tar2.create( { gzip: true, file: archivePath, // Go one level up to include the repository name in the archive cwd: path.resolve(archivePath, ".."), portable: true }, [info.type] ) ) ).then( () => rm(path.resolve(directory), { recursive: true }) ).then(resolve2).catch((error) => reject(new GitlyCloneError(error.message))); } else { reject(new GitlyCloneError("Failed to clone the repository")); } }); }); return archivePath; }; if (await isOffline() || options.cache) { order = [local]; } else if (options.force || ["master", "main"].includes(info.type)) { order = [remote, local]; } try { const result = await execute(order); if (typeof result === "boolean") { return archivePath; } return result; } catch (error) { if (options.throw) { throw error; } } return ""; } // src/utils/download.ts import { rm as rm2 } from "shelljs"; // src/utils/fetch.ts import axios from "axios"; import * as stream from "stream"; import { promisify } from "util"; // src/utils/write.ts import { createWriteStream, promises as fs2 } from "fs"; import { dirname, normalize } from "path"; var { mkdir } = fs2; async function write(path2) { const _path = normalize(path2); await mkdir(dirname(_path), { recursive: true }); return createWriteStream(_path); } // src/utils/fetch.ts var pipeline2 = promisify(stream.pipeline); async function fetch(url, file, options = {}) { const response = await axios.get(url, { headers: options.headers, responseType: "stream", validateStatus: (status) => status >= 200 && status < 500 }); const { statusText: message, status: code } = response; if (code >= 400) throw new GitlyDownloadError(message, code); if (code >= 300 && code < 400 && response.headers.location) { return fetch(response.headers.location, file); } await pipeline2(response.data, await write(file)); return file; } // src/utils/download.ts async function download(repository, options = {}) { const info = parse(repository, options); const archivePath = getArchivePath(info, options); const url = getArchiveUrl(info, options); const local = async () => exists(archivePath); const remote = async () => { if (await exists(archivePath)) { rm2(archivePath); } return fetch(url, archivePath, options); }; let order = [local, remote]; if (await isOffline() || options.cache) { order = [local]; } else if (options.force || ["master", "main"].includes(info.type)) { order = [remote, local]; } try { const result = await execute(order); if (typeof result === "boolean") { return archivePath; } return result; } catch (error) { if (options.throw) { throw error; } } return ""; } // src/utils/extract.ts import { promises as fs3 } from "fs"; import { resolve } from "path"; var { mkdir: mkdir2 } = fs3; var extract_default = async (source, destination, options = {}) => { var _a6; const _destination = resolve(destination); if (await exists(source, options)) { try { const filter = ((_a6 = options.extract) == null ? void 0 : _a6.filter) ? options.extract.filter : () => true; await mkdir2(destination, { recursive: true }); await extract2({ strip: 1, filter, file: source, cwd: _destination }); return _destination; } catch (_) { } } return ""; }; // src/utils/gitly.ts async function gitly(repository, destination, options) { let source = ""; switch (options == null ? void 0 : options.backend) { case "git": source = await clone(repository, options); break; default: source = await download(repository, options); break; } return [source, await extract_default(source, destination, options)]; } export { clone, gitly as default, download, extract_default as extract, parse }; //# sourceMappingURL=main.js.map