From 539fae169c07cc0c511c0b692f9b76f026e1cac5 Mon Sep 17 00:00:00 2001 From: Souma <101255979+5ouma@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:54:02 +0900 Subject: [PATCH] feat(hono): Make workable on any runtime (#64) Use the env adapter to avoid Deno-dedicated API. --- deno.json | 4 ++-- src/libs/content.test.ts | 11 ++++------- src/libs/content.ts | 36 +++++++++++++++++++++++++++++------- src/libs/env.test.ts | 24 ------------------------ src/libs/env.ts | 37 ------------------------------------- src/libs/mod.ts | 1 + src/libs/redirect.test.ts | 8 +++----- src/libs/redirect.ts | 33 +++++++++++++++++++++++++++------ src/libs/test_utils.ts | 14 -------------- src/server.ts | 21 +++++++++++++++++++-- 10 files changed, 85 insertions(+), 104 deletions(-) delete mode 100644 src/libs/env.test.ts delete mode 100644 src/libs/env.ts diff --git a/deno.json b/deno.json index 3c97cd9..a87f978 100644 --- a/deno.json +++ b/deno.json @@ -8,10 +8,10 @@ }, "fmt": { "exclude": ["LICENSE", "README.md", ".github/**/*.md"] }, "tasks": { - "run": "deno run --env='.env' --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH,GITHUB_TOKEN' --allow-net='0.0.0.0,api.github.com'", + "run": "deno run --env='.env' --allow-env --allow-net='0.0.0.0,api.github.com'", "start": "deno task run src/server.ts", "dev": "deno task run --watch src/server.ts", - "test": "deno test --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH,GITHUB_TOKEN' --allow-net='0.0.0.0,api.github.com' --parallel --shuffle", + "test": "deno test --allow-env --allow-net='0.0.0.0,api.github.com' --parallel --shuffle", "cov": "deno task test --coverage && deno coverage --lcov > coverage.lcov" }, "imports": { diff --git a/src/libs/content.test.ts b/src/libs/content.test.ts index fe05eba..3eb9d0b 100644 --- a/src/libs/content.test.ts +++ b/src/libs/content.test.ts @@ -2,28 +2,25 @@ import { assertEquals, assertExists, assertStringIncludes } from "@std/assert"; import { STATUS_CODE } from "@std/http/status"; import { getContent } from "./content.ts"; -import { exportRepo, testRef, testRepo, unknownRepo } from "./test_utils.ts"; +import { testRef, testRepo, unknownRepo } from "./test_utils.ts"; Deno.test("Get Content", async (t: Deno.TestContext) => { await t.step("normal", async () => { - exportRepo(testRepo); - const [data, status] = await getContent(); + const [data, status] = await getContent(testRepo); assertExists(data); assertEquals(status, STATUS_CODE.OK); }); await t.step("with ref", async () => { - exportRepo(testRepo); - const [data, status] = await getContent(testRef); + const [data, status] = await getContent(testRepo, testRef); assertExists(data); assertEquals(status, STATUS_CODE.OK); }); await t.step("not found", async () => { - exportRepo(unknownRepo); - const [data, status] = await getContent(); + const [data, status] = await getContent(unknownRepo); assertStringIncludes(data, `⚠️ ${STATUS_CODE.NotFound}:`); assertEquals(status, STATUS_CODE.NotFound); diff --git a/src/libs/content.ts b/src/libs/content.ts index bdaaf74..b81f117 100644 --- a/src/libs/content.ts +++ b/src/libs/content.ts @@ -2,7 +2,7 @@ import { Octokit } from "@octokit/rest"; import type { StatusCode } from "@std/http"; export type { StatusCode }; -import { getRepository, githubToken } from "./env.ts"; +import type { Repository } from "./types.ts"; /** * Get the content of the repository. @@ -12,29 +12,51 @@ import { getRepository, githubToken } from "./env.ts"; * * @example Use the default branch * ```ts - * const [content, status] = await getContent(); + * const repository: Repository = { + * owner: "denoland", + * name: "deno", + * path: "README.md", + * }; + * const [content, status] = await getContent(repository); * ``` * @example Use a specific branch * ```ts + * const repository: Repository = { + * owner: "denoland", + * name: "deno", + * path: "README.md", + * }; * const branch = "main"; - * const [content, status] = await getContent(branch); + * const [content, status] = await getContent(repository, branch); * ``` * @example Use a specific tag * ```ts + * const repository: Repository = { + * owner: "denoland", + * name: "deno", + * path: "README.md", + * }; * const tag = "v1.0.0"; - * const [content, status] = await getContent(tag); + * const [content, status] = await getContent(repository, tag); * ``` * @example Use a specific commit * ```ts + * const repository: Repository = { + * owner: "denoland", + * name: "deno", + * path: "README.md", + * }; * const commit = "a1b2c3d4e5f6"; - * const [content, status] = await getContent(commit); + * const [content, status] = await getContent(repository, commit); * ``` */ + export async function getContent( + repository: Repository, ref: string | undefined = undefined, + token: string | undefined = undefined, ): Promise<[string, StatusCode]> { - const octokit = new Octokit({ auth: githubToken }); - const repository = getRepository(); + const octokit = new Octokit({ auth: token }); try { const { status, data } = await octokit.rest.repos.getContent({ diff --git a/src/libs/env.test.ts b/src/libs/env.test.ts deleted file mode 100644 index 0a1d47c..0000000 --- a/src/libs/env.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { assertEquals } from "@std/assert"; - -import { getRepository } from "./env.ts"; -import { clearRepo, exportRepo, testRepo } from "./test_utils.ts"; - -Deno.test("Get Repository Env", async (t: Deno.TestContext) => { - await t.step("Normal", () => { - exportRepo(testRepo); - const repository = getRepository(); - - assertEquals(repository.owner, testRepo.owner); - assertEquals(repository.name, testRepo.name); - assertEquals(repository.path, testRepo.path); - }); - - await t.step("Not set", () => { - clearRepo(); - const repository = getRepository(); - - assertEquals(repository.owner, ""); - assertEquals(repository.name, ""); - assertEquals(repository.path, ""); - }); -}); diff --git a/src/libs/env.ts b/src/libs/env.ts deleted file mode 100644 index 2016570..0000000 --- a/src/libs/env.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Repository } from "./types.ts"; - -/** - * Get the repository details from the environment variables. - * - * @returns {Repository} The repository type - * - * @example - * ```ts - * const repository = getRepository(); - * ``` - */ -export function getRepository(): Repository { - const repository: Repository = { - owner: Deno.env.get("REPOSITORY_OWNER") ?? "", - name: Deno.env.get("REPOSITORY_NAME") ?? "", - path: Deno.env.get("REPOSITORY_PATH") ?? "", - }; - - for (const key in repository) { - if (!repository[key as keyof Repository]) { - console.error(`🚨 missing: $REPOSITORY_${key.toUpperCase()}`); - } - } - - return repository; -} - -/** - * The GitHub token from the environment variables. - * - * @example - * ```ts - * const token = githubToken; - * ``` - */ -export const githubToken: string | undefined = Deno.env.get("GITHUB_TOKEN"); diff --git a/src/libs/mod.ts b/src/libs/mod.ts index 7311f3e..00c3b9e 100644 --- a/src/libs/mod.ts +++ b/src/libs/mod.ts @@ -1,2 +1,3 @@ export * from "./content.ts"; export * from "./redirect.ts"; +export type { Repository } from "./types.ts"; diff --git a/src/libs/redirect.test.ts b/src/libs/redirect.test.ts index 0054c3c..4c03d95 100644 --- a/src/libs/redirect.test.ts +++ b/src/libs/redirect.test.ts @@ -1,20 +1,18 @@ import { assertEquals } from "@std/assert"; import { checkRedirect } from "./redirect.ts"; -import { exportRepo, testRef, testRepo, testUserAgent } from "./test_utils.ts"; +import { testRef, testRepo, testUserAgent } from "./test_utils.ts"; import { getGitHubUrl } from "./utils.ts"; Deno.test("Redirect Detection", async (t: Deno.TestContext) => { await t.step("normal", () => { - exportRepo(testRepo); - const url: URL | null = checkRedirect(testUserAgent); + const url: URL | null = checkRedirect(testUserAgent, testRepo); assertEquals(url, getGitHubUrl(testRepo)); }); await t.step("with ref", () => { - exportRepo(testRepo); - const url: URL | null = checkRedirect(testUserAgent, testRef); + const url: URL | null = checkRedirect(testUserAgent, testRepo, testRef); assertEquals(url, getGitHubUrl(testRepo, testRef)); }); diff --git a/src/libs/redirect.ts b/src/libs/redirect.ts index 386837a..5dc822f 100644 --- a/src/libs/redirect.ts +++ b/src/libs/redirect.ts @@ -1,7 +1,7 @@ import type { UserAgent } from "@std/http/user-agent"; export type { UserAgent }; -import { getRepository } from "./env.ts"; +import type { Repository } from "./types.ts"; import { getGitHubUrl } from "./utils.ts"; /** @@ -14,32 +14,53 @@ import { getGitHubUrl } from "./utils.ts"; * @example Use the default branch * ```ts * const userAgent = new UserAgent("Chrome/1.2.3"); - * const url: URL | null = checkRedirect(userAgent); + * const repository: Repository = { + * owner: "denoland", + * name: "deno", + * path: "README.md", + * }; + * const url: URL | null = checkRedirect(userAgent, repository); * ``` * @example Use a specific branch * ```ts * const userAgent = new UserAgent("Chrome/1.2.3"); + * const repository: Repository = { + * owner: "denoland", + * name: "deno", + * path: "README.md", + * }; * const branch = "main"; - * const url: URL | null = checkRedirect(userAgent, branch); + * const url: URL | null = checkRedirect(userAgent, repository, branch); * ``` * @example Use a specific tag * ```ts * const userAgent = new UserAgent("Chrome/1.2.3"); + * const repository: Repository = { + * owner: "denoland", + * name: "deno", + * path: "README.md", + * }; * const tag = "v1.0.0"; - * const url: URL | null = checkRedirect(userAgent, tag); + * const url: URL | null = checkRedirect(userAgent, repository, tag); * ``` * @example Use a specific commit * ```ts * const userAgent = new UserAgent("Chrome/1.2.3"); + * const repository: Repository = { + * owner: "denoland", + * name: "deno", + * path: "README.md", + * }; * const commit = "a1b2c3d4e5f6"; - * const url: URL | null = checkRedirect(userAgent, commit); + * const url: URL | null = checkRedirect(userAgent, repository, commit); * ``` */ export function checkRedirect( userAgent: UserAgent, + repository: Repository, ref: string = "master", ): URL | null { - const url = getGitHubUrl(getRepository(), ref); + const url = getGitHubUrl(repository, ref); return userAgent?.browser.name ? url : null; } diff --git a/src/libs/test_utils.ts b/src/libs/test_utils.ts index fe55859..c589cfc 100644 --- a/src/libs/test_utils.ts +++ b/src/libs/test_utils.ts @@ -66,17 +66,3 @@ export function exportRepo(repository: Repository) { Deno.env.set("REPOSITORY_NAME", repository.name); Deno.env.set("REPOSITORY_PATH", repository.path); } - -/** - * Clear the repository details from the environment variables. - * - * @example - * ```ts - * clearRepo(); - * ``` - */ -export function clearRepo() { - Deno.env.delete("REPOSITORY_OWNER"); - Deno.env.delete("REPOSITORY_NAME"); - Deno.env.delete("REPOSITORY_PATH"); -} diff --git a/src/server.ts b/src/server.ts index 2b16058..069d7a6 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,10 +1,11 @@ import { type Context, Hono } from "@hono/hono"; export type { Hono }; +import { env } from "@hono/hono/adapter"; import { logger } from "@hono/hono/logger"; import { STATUS_CODE } from "@std/http/status"; import { UserAgent } from "@std/http/user-agent"; -import { checkRedirect, getContent } from "./libs/mod.ts"; +import { checkRedirect, getContent, type Repository } from "./libs/mod.ts"; /** * The Hono application for this project. @@ -24,15 +25,31 @@ export const app: Hono = new Hono(); app.use(logger()); app .get("/:ref?", async (ctx: Context) => { + const { REPOSITORY_OWNER, REPOSITORY_NAME, REPOSITORY_PATH, GITHUB_TOKEN } = + env< + { + REPOSITORY_OWNER: string; + REPOSITORY_NAME: string; + REPOSITORY_PATH: string; + GITHUB_TOKEN: string; + } + >(ctx); + const repository: Repository = { + owner: REPOSITORY_OWNER, + name: REPOSITORY_NAME, + path: REPOSITORY_PATH, + }; const ref: string = ctx.req.param("ref"); + const url: URL | null = checkRedirect( new UserAgent(ctx.req.header("User-Agent") ?? ""), + repository, ref, ); return url ? ctx.redirect(url.toString(), STATUS_CODE.PermanentRedirect) - : ctx.text(...await getContent(ref)); + : ctx.text(...await getContent(repository, ref, GITHUB_TOKEN)); }) .get("*", (ctx: Context) => { return ctx.redirect("/", STATUS_CODE.SeeOther);