From 4d7d76939aac9071912f6f1a754dd8b223c5efc1 Mon Sep 17 00:00:00 2001 From: Simon Gander Date: Fri, 19 Jul 2024 23:06:47 +0200 Subject: [PATCH] refactor: move each provider to own file --- src/_utils.ts | 11 ++- src/providers.ts | 139 ------------------------------------- src/providers/bitbucket.ts | 15 ++++ src/providers/github.ts | 24 +++++++ src/providers/gitlab.ts | 18 +++++ src/providers/http.ts | 61 ++++++++++++++++ src/providers/index.ts | 16 +++++ src/providers/sourcehut.ts | 15 ++++ 8 files changed, 159 insertions(+), 140 deletions(-) delete mode 100644 src/providers.ts create mode 100644 src/providers/bitbucket.ts create mode 100644 src/providers/github.ts create mode 100644 src/providers/gitlab.ts create mode 100644 src/providers/http.ts create mode 100644 src/providers/index.ts create mode 100644 src/providers/sourcehut.ts diff --git a/src/_utils.ts b/src/_utils.ts index f6d56db..af88d2c 100644 --- a/src/_utils.ts +++ b/src/_utils.ts @@ -7,7 +7,7 @@ import { promisify } from "node:util"; import type { Agent } from "node:http"; import { relative, resolve } from "pathe"; import { fetch } from "node-fetch-native/proxy"; -import type { GitInfo } from "./types"; +import type { GitInfo, TemplateInfo } from "./types"; export async function download( url: string, @@ -136,3 +136,12 @@ export function startShell(cwd: string) { stdio: "inherit", }); } + +export function defineProvider( + provider: ( + input: string, + options: { auth?: string }, + ) => TemplateInfo | Promise | null, +) { + return provider; +} diff --git a/src/providers.ts b/src/providers.ts deleted file mode 100644 index ba8ce43..0000000 --- a/src/providers.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { basename } from "pathe"; -import type { TemplateInfo, TemplateProvider } from "./types"; -import { debug, parseGitURI, sendFetch } from "./_utils"; - -export const http: TemplateProvider = async (input, options) => { - if (input.endsWith(".json")) { - return (await _httpJSON(input, options)) as TemplateInfo; - } - - const url = new URL(input); - let name: string = basename(url.pathname); - - try { - const head = await sendFetch(url.href, { - method: "HEAD", - validateStatus: true, - headers: { - authorization: options.auth ? `Bearer ${options.auth}` : undefined, - }, - }); - const _contentType = head.headers.get("content-type") || ""; - if (_contentType.includes("application/json")) { - return (await _httpJSON(input, options)) as TemplateInfo; - } - const filename = head.headers - .get("content-disposition") - ?.match(/filename="?(.+)"?/)?.[1]; - if (filename) { - name = filename.split(".")[0]; - } - } catch (error) { - debug(`Failed to fetch HEAD for ${url.href}:`, error); - } - - return { - name: `${name}-${url.href.slice(0, 8)}`, - version: "", - subdir: "", - tar: url.href, - defaultDir: name, - headers: { - Authorization: options.auth ? `Bearer ${options.auth}` : undefined, - }, - }; -}; - -const _httpJSON: TemplateProvider = async (input, options) => { - const result = await sendFetch(input, { - validateStatus: true, - headers: { - authorization: options.auth ? `Bearer ${options.auth}` : undefined, - }, - }); - const info = (await result.json()) as TemplateInfo; - if (!info.tar || !info.name) { - throw new Error( - `Invalid template info from ${input}. name or tar fields are missing!`, - ); - } - return info; -}; - -export const github: TemplateProvider = (input, options) => { - const parsed = parseGitURI(input); - - // https://docs.github.com/en/rest/repos/contents#download-a-repository-archive-tar - // TODO: Verify solution for github enterprise - const githubAPIURL = process.env.GIGET_GITHUB_URL || "https://api.github.com"; - - return { - name: parsed.repo.replace("/", "-"), - version: parsed.ref, - subdir: parsed.subdir, - headers: { - Authorization: options.auth ? `Bearer ${options.auth}` : undefined, - Accept: "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - }, - url: `${githubAPIURL.replace("api.github.com", "github.com")}/${ - parsed.repo - }/tree/${parsed.ref}${parsed.subdir}`, - tar: `${githubAPIURL}/repos/${parsed.repo}/tarball/${parsed.ref}`, - }; -}; - -export const gitlab: TemplateProvider = (input, options) => { - const parsed = parseGitURI(input); - const gitlab = process.env.GIGET_GITLAB_URL || "https://gitlab.com"; - return { - name: parsed.repo.replace("/", "-"), - version: parsed.ref, - subdir: parsed.subdir, - headers: { - authorization: options.auth ? `Bearer ${options.auth}` : undefined, - // https://gitlab.com/gitlab-org/gitlab/-/commit/50c11f278d18fe1f3fb12eb595067216bb58ade2 - "sec-fetch-mode": "same-origin", - }, - url: `${gitlab}/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`, - tar: `${gitlab}/${parsed.repo}/-/archive/${parsed.ref}.tar.gz`, - }; -}; - -export const bitbucket: TemplateProvider = (input, options) => { - const parsed = parseGitURI(input); - return { - name: parsed.repo.replace("/", "-"), - version: parsed.ref, - subdir: parsed.subdir, - headers: { - authorization: options.auth ? `Bearer ${options.auth}` : undefined, - }, - url: `https://bitbucket.com/${parsed.repo}/src/${parsed.ref}${parsed.subdir}`, - tar: `https://bitbucket.org/${parsed.repo}/get/${parsed.ref}.tar.gz`, - }; -}; - -export const sourcehut: TemplateProvider = (input, options) => { - const parsed = parseGitURI(input); - return { - name: parsed.repo.replace("/", "-"), - version: parsed.ref, - subdir: parsed.subdir, - headers: { - authorization: options.auth ? `Bearer ${options.auth}` : undefined, - }, - url: `https://git.sr.ht/~${parsed.repo}/tree/${parsed.ref}/item${parsed.subdir}`, - tar: `https://git.sr.ht/~${parsed.repo}/archive/${parsed.ref}.tar.gz`, - }; -}; - -export const providers: Record = { - http, - https: http, - github, - gh: github, - gitlab, - bitbucket, - sourcehut, -}; diff --git a/src/providers/bitbucket.ts b/src/providers/bitbucket.ts new file mode 100644 index 0000000..ab15238 --- /dev/null +++ b/src/providers/bitbucket.ts @@ -0,0 +1,15 @@ +import { defineProvider, parseGitURI } from "../_utils"; + +export default defineProvider((input, options) => { + const parsed = parseGitURI(input); + return { + name: parsed.repo.replace("/", "-"), + version: parsed.ref, + subdir: parsed.subdir, + headers: { + authorization: options.auth ? `Bearer ${options.auth}` : undefined, + }, + url: `https://bitbucket.com/${parsed.repo}/src/${parsed.ref}${parsed.subdir}`, + tar: `https://bitbucket.org/${parsed.repo}/get/${parsed.ref}.tar.gz`, + }; +}); diff --git a/src/providers/github.ts b/src/providers/github.ts new file mode 100644 index 0000000..9d55602 --- /dev/null +++ b/src/providers/github.ts @@ -0,0 +1,24 @@ +import { defineProvider, parseGitURI } from "../_utils"; + +export default defineProvider((input, options) => { + const parsed = parseGitURI(input); + + // https://docs.github.com/en/rest/repos/contents#download-a-repository-archive-tar + // TODO: Verify solution for github enterprise + const githubAPIURL = process.env.GIGET_GITHUB_URL || "https://api.github.com"; + + return { + name: parsed.repo.replace("/", "-"), + version: parsed.ref, + subdir: parsed.subdir, + headers: { + Authorization: options.auth ? `Bearer ${options.auth}` : undefined, + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }, + url: `${githubAPIURL.replace("api.github.com", "github.com")}/${ + parsed.repo + }/tree/${parsed.ref}${parsed.subdir}`, + tar: `${githubAPIURL}/repos/${parsed.repo}/tarball/${parsed.ref}`, + }; +}); diff --git a/src/providers/gitlab.ts b/src/providers/gitlab.ts new file mode 100644 index 0000000..11d4254 --- /dev/null +++ b/src/providers/gitlab.ts @@ -0,0 +1,18 @@ +import { defineProvider, parseGitURI } from "../_utils"; + +export default defineProvider((input, options) => { + const parsed = parseGitURI(input); + const gitlab = process.env.GIGET_GITLAB_URL || "https://gitlab.com"; + return { + name: parsed.repo.replace("/", "-"), + version: parsed.ref, + subdir: parsed.subdir, + headers: { + authorization: options.auth ? `Bearer ${options.auth}` : undefined, + // https://gitlab.com/gitlab-org/gitlab/-/commit/50c11f278d18fe1f3fb12eb595067216bb58ade2 + "sec-fetch-mode": "same-origin", + }, + url: `${gitlab}/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`, + tar: `${gitlab}/${parsed.repo}/-/archive/${parsed.ref}.tar.gz`, + }; +}); diff --git a/src/providers/http.ts b/src/providers/http.ts new file mode 100644 index 0000000..23a11d4 --- /dev/null +++ b/src/providers/http.ts @@ -0,0 +1,61 @@ +import { basename } from "pathe"; +import { debug, defineProvider, sendFetch } from "../_utils"; +import { TemplateInfo } from "../types"; + +const _httpJSON = defineProvider(async (input, options) => { + const result = await sendFetch(input, { + validateStatus: true, + headers: { + authorization: options.auth ? `Bearer ${options.auth}` : undefined, + }, + }); + const info = (await result.json()) as TemplateInfo; + if (!info.tar || !info.name) { + throw new Error( + `Invalid template info from ${input}. name or tar fields are missing!`, + ); + } + return info; +}); + +export default defineProvider(async (input, options) => { + if (input.endsWith(".json")) { + return (await _httpJSON(input, options)) as TemplateInfo; + } + + const url = new URL(input); + let name: string = basename(url.pathname); + + try { + const head = await sendFetch(url.href, { + method: "HEAD", + validateStatus: true, + headers: { + authorization: options.auth ? `Bearer ${options.auth}` : undefined, + }, + }); + const _contentType = head.headers.get("content-type") || ""; + if (_contentType.includes("application/json")) { + return (await _httpJSON(input, options)) as TemplateInfo; + } + const filename = head.headers + .get("content-disposition") + ?.match(/filename="?(.+)"?/)?.[1]; + if (filename) { + name = filename.split(".")[0]; + } + } catch (error) { + debug(`Failed to fetch HEAD for ${url.href}:`, error); + } + + return { + name: `${name}-${url.href.slice(0, 8)}`, + version: "", + subdir: "", + tar: url.href, + defaultDir: name, + headers: { + Authorization: options.auth ? `Bearer ${options.auth}` : undefined, + }, + }; +}); diff --git a/src/providers/index.ts b/src/providers/index.ts new file mode 100644 index 0000000..5992471 --- /dev/null +++ b/src/providers/index.ts @@ -0,0 +1,16 @@ +import { TemplateProvider } from "../types"; +import http from "./http"; +import github from "./github"; +import gitlab from "./gitlab"; +import bitbucket from "./bitbucket"; +import sourcehut from "./sourcehut"; + +export const providers: Record = { + http, + https: http, + github, + gh: github, + gitlab, + bitbucket, + sourcehut, +}; diff --git a/src/providers/sourcehut.ts b/src/providers/sourcehut.ts new file mode 100644 index 0000000..0e97b54 --- /dev/null +++ b/src/providers/sourcehut.ts @@ -0,0 +1,15 @@ +import { defineProvider, parseGitURI } from "../_utils"; + +export default defineProvider((input, options) => { + const parsed = parseGitURI(input); + return { + name: parsed.repo.replace("/", "-"), + version: parsed.ref, + subdir: parsed.subdir, + headers: { + authorization: options.auth ? `Bearer ${options.auth}` : undefined, + }, + url: `https://git.sr.ht/~${parsed.repo}/tree/${parsed.ref}/item${parsed.subdir}`, + tar: `https://git.sr.ht/~${parsed.repo}/archive/${parsed.ref}.tar.gz`, + }; +});