From 8e9d2ac92d2ceb09749a8df8a5eb0c78d9d8e5f4 Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Tue, 4 Jun 2024 22:53:56 +0900 Subject: [PATCH 01/19] feat(init): Deno init with some files This project is created with Deno. --- deno.json | 8 ++++++++ deno.lock | 4 ++++ src/server.ts | 1 + 3 files changed, 13 insertions(+) create mode 100644 deno.json create mode 100644 deno.lock create mode 100644 src/server.ts diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..7463c5d --- /dev/null +++ b/deno.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://deno.land/x/deno/cli/schemas/config-file.v1.json", + "fmt": { "exclude": ["LICENSE", ".github/**/*.md"] }, + "test": { "include": ["src/", "test/"] }, + "tasks": { + "start": "deno run ./src/server.ts" + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..37f10ce --- /dev/null +++ b/deno.lock @@ -0,0 +1,4 @@ +{ + "version": "3", + "remote": {} +} diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..b796754 --- /dev/null +++ b/src/server.ts @@ -0,0 +1 @@ +console.log("Hello, Deno!"); From f33da18eb55f544f906e44cafed1b7f8b4e957b7 Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Tue, 4 Jun 2024 23:19:42 +0900 Subject: [PATCH 02/19] feat(server): Use Oak to host a HTTP server Return a plain text for the test. --- deno.json | 8 ++- deno.lock | 147 +++++++++++++++++++++++++++++++++++++++++++- src/router.ts | 9 +++ src/server.ts | 10 ++- test/router_test.ts | 15 +++++ 5 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 src/router.ts create mode 100644 test/router_test.ts diff --git a/deno.json b/deno.json index 7463c5d..3f62780 100644 --- a/deno.json +++ b/deno.json @@ -3,6 +3,12 @@ "fmt": { "exclude": ["LICENSE", ".github/**/*.md"] }, "test": { "include": ["src/", "test/"] }, "tasks": { - "start": "deno run ./src/server.ts" + "start": "deno run --allow-net='0.0.0.0' ./src/server.ts", + "test": "deno test --parallel --shuffle" + }, + "imports": { + "@oak/oak": "jsr:@oak/oak@16.0.0", + "@std/assert": "jsr:@std/assert@0.226.0", + "@std/http": "jsr:@std/http@0.224.3" } } diff --git a/deno.lock b/deno.lock index 37f10ce..1217247 100644 --- a/deno.lock +++ b/deno.lock @@ -1,4 +1,149 @@ { "version": "3", - "remote": {} + "packages": { + "specifiers": { + "jsr:@oak/commons@0.10": "jsr:@oak/commons@0.10.1", + "jsr:@oak/oak@16.0.0": "jsr:@oak/oak@16.0.0", + "jsr:@std/assert@0.222": "jsr:@std/assert@0.222.1", + "jsr:@std/assert@0.223": "jsr:@std/assert@0.223.0", + "jsr:@std/assert@0.226.0": "jsr:@std/assert@0.226.0", + "jsr:@std/assert@^0.222.1": "jsr:@std/assert@0.222.1", + "jsr:@std/assert@^0.223.0": "jsr:@std/assert@0.223.0", + "jsr:@std/bytes@0.222": "jsr:@std/bytes@0.222.1", + "jsr:@std/bytes@0.223": "jsr:@std/bytes@0.223.0", + "jsr:@std/bytes@^0.223.0": "jsr:@std/bytes@0.223.0", + "jsr:@std/crypto@0.222": "jsr:@std/crypto@0.222.1", + "jsr:@std/crypto@0.223": "jsr:@std/crypto@0.223.0", + "jsr:@std/encoding@^0.222.1": "jsr:@std/encoding@0.222.1", + "jsr:@std/encoding@^0.223.0": "jsr:@std/encoding@0.223.0", + "jsr:@std/http@0.222": "jsr:@std/http@0.222.1", + "jsr:@std/http@0.223": "jsr:@std/http@0.223.0", + "jsr:@std/http@0.224.3": "jsr:@std/http@0.224.3", + "jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.0", + "jsr:@std/io@0.223": "jsr:@std/io@0.223.0", + "jsr:@std/media-types@0.222": "jsr:@std/media-types@0.222.1", + "jsr:@std/media-types@0.223": "jsr:@std/media-types@0.223.0", + "jsr:@std/path@0.223": "jsr:@std/path@0.223.0", + "npm:@types/node": "npm:@types/node@18.16.19", + "npm:path-to-regexp@6.2.1": "npm:path-to-regexp@6.2.1" + }, + "jsr": { + "@oak/commons@0.10.1": { + "integrity": "4775ebf70782b0c5d95958d85b2425b3fbdff06f57654aa4143dbcb23b923cf4", + "dependencies": [ + "jsr:@std/assert@0.222", + "jsr:@std/bytes@0.222", + "jsr:@std/crypto@0.222", + "jsr:@std/http@0.222", + "jsr:@std/media-types@0.222" + ] + }, + "@oak/oak@16.0.0": { + "integrity": "a98756fb4bf69f728c9a5179ff4157620946c5969bcaa3d6bd64cca8b198f74e", + "dependencies": [ + "jsr:@oak/commons@0.10", + "jsr:@std/assert@0.223", + "jsr:@std/bytes@0.223", + "jsr:@std/crypto@0.223", + "jsr:@std/http@0.223", + "jsr:@std/io@0.223", + "jsr:@std/media-types@0.223", + "jsr:@std/path@0.223", + "npm:path-to-regexp@6.2.1" + ] + }, + "@std/assert@0.222.1": { + "integrity": "691637161ee584a9919d1f9950ddd1272feb8e0a19e83aa5b7563cedaf73d74c" + }, + "@std/assert@0.223.0": { + "integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24" + }, + "@std/assert@0.226.0": { + "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3", + "dependencies": [ + "jsr:@std/internal@^1.0.0" + ] + }, + "@std/bytes@0.222.1": { + "integrity": "04dbf33e889ed5f9f6a87bc6b6b882dc105fffae6d2b042f832d92d4f34771c0" + }, + "@std/bytes@0.223.0": { + "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8" + }, + "@std/crypto@0.222.1": { + "integrity": "d5b9e6c704fadbcc384cd42c0b603ad4aea710ece0ff26426602681b64fd237c", + "dependencies": [ + "jsr:@std/assert@^0.222.1" + ] + }, + "@std/crypto@0.223.0": { + "integrity": "1aa9555ff56b09e197ad988ea200f84bc6781fd4fd83f3a156ee44449af93000", + "dependencies": [ + "jsr:@std/assert@^0.223.0", + "jsr:@std/encoding@^0.223.0" + ] + }, + "@std/encoding@0.222.1": { + "integrity": "fb6c1d38722feebc8d4a5efa3eb2039ecec0d50d053186240484d0c4a4ce1006" + }, + "@std/encoding@0.223.0": { + "integrity": "2b5615a75e00337ce113f34cf2f9b8c18182c751a8dcc8b1a2c2fc0e117bef00" + }, + "@std/http@0.222.1": { + "integrity": "a3c731ec6040927aa37d8378b9e6b467e2e3068c1f97838a6e79ee3bdc103521", + "dependencies": [ + "jsr:@std/encoding@^0.222.1" + ] + }, + "@std/http@0.223.0": { + "integrity": "15ab8a0c5a7e9d5be017a15b01600f20f66602ceec48b378939fa24fcec522aa", + "dependencies": [ + "jsr:@std/assert@^0.223.0", + "jsr:@std/encoding@^0.223.0" + ] + }, + "@std/http@0.224.3": { + "integrity": "dac3a3640c730b9a4bcdb916b5231f4f2f5c141daf8dafc2b46011e1dab08eed" + }, + "@std/internal@1.0.0": { + "integrity": "ac6a6dfebf838582c4b4f61a6907374e27e05bedb6ce276e0f1608fe84e7cd9a" + }, + "@std/io@0.223.0": { + "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1", + "dependencies": [ + "jsr:@std/bytes@^0.223.0" + ] + }, + "@std/media-types@0.222.1": { + "integrity": "147cbd7f29fb4480625ccdad679637f945437f455534049eb7c95977b973a137" + }, + "@std/media-types@0.223.0": { + "integrity": "84684680c2eb6bc6d9369c6d6f26a49decaf2c7603ff531862dda575d9d6776e" + }, + "@std/path@0.223.0": { + "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989", + "dependencies": [ + "jsr:@std/assert@^0.223.0" + ] + } + }, + "npm": { + "@types/node@18.16.19": { + "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", + "dependencies": {} + }, + "path-to-regexp@6.2.1": { + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", + "dependencies": {} + } + } + }, + "remote": {}, + "workspace": { + "dependencies": [ + "jsr:@oak/oak@16.0.0", + "jsr:@std/assert@0.226.0", + "jsr:@std/http@0.224.3" + ] + } } diff --git a/src/router.ts b/src/router.ts new file mode 100644 index 0000000..e429b81 --- /dev/null +++ b/src/router.ts @@ -0,0 +1,9 @@ +import { Router, type RouterContext } from "@oak/oak"; +import { STATUS_CODE } from "@std/http/status"; + +export const router = new Router(); +router.get("/", (ctx: RouterContext) => { + ctx.response.type = "text/plain"; + ctx.response.status = STATUS_CODE.OK; + ctx.response.body = "Hello, Deno!"; +}); diff --git a/src/server.ts b/src/server.ts index b796754..1144ac9 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1 +1,9 @@ -console.log("Hello, Deno!"); +import { Application } from "@oak/oak"; +import { router } from "./router.ts"; + +const app = new Application(); + +app.use(router.routes()); +app.use(router.allowedMethods()); + +await app.listen({ port: 8080 }); diff --git a/test/router_test.ts b/test/router_test.ts new file mode 100644 index 0000000..add0567 --- /dev/null +++ b/test/router_test.ts @@ -0,0 +1,15 @@ +import { testing, type RouterContext } from "@oak/oak"; +import { assertEquals } from "@std/assert"; +import { STATUS_CODE } from "@std/http/status"; +import { router } from "../src/router.ts"; + +Deno.test("Root Path", async () => { + const ctx: RouterContext = testing.createMockContext({ + method: "GET", + path: "/", + }); + await router.routes()(ctx, () => Promise.resolve()); + + assertEquals(ctx.response.body, "Hello, Deno!"); + assertEquals(ctx.response.status, STATUS_CODE.OK); +}); From ee91017f0016bfe60f8f7a58778ddf6386865b05 Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Wed, 5 Jun 2024 00:16:22 +0900 Subject: [PATCH 03/19] feat(content): Get raw file from GitHub Get and return the file as plain text. --- deno.json | 5 +++-- deno.lock | 24 ++++++++++++++++++------ src/libs/content.ts | 26 ++++++++++++++++++++++++++ src/libs/mod.ts | 1 + src/router.ts | 7 +++---- test/libs/content_test.ts | 25 +++++++++++++++++++++++++ test/router_test.ts | 1 - 7 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 src/libs/content.ts create mode 100644 src/libs/mod.ts create mode 100644 test/libs/content_test.ts diff --git a/deno.json b/deno.json index 3f62780..acb859b 100644 --- a/deno.json +++ b/deno.json @@ -3,11 +3,12 @@ "fmt": { "exclude": ["LICENSE", ".github/**/*.md"] }, "test": { "include": ["src/", "test/"] }, "tasks": { - "start": "deno run --allow-net='0.0.0.0' ./src/server.ts", - "test": "deno test --parallel --shuffle" + "start": "deno run --allow-net='0.0.0.0,api.github.com' ./src/server.ts", + "test": "deno test --allow-net='api.github.com' --parallel --shuffle" }, "imports": { "@oak/oak": "jsr:@oak/oak@16.0.0", + "@octokit/rest": "https://esm.sh/@octokit/rest@20.1.1", "@std/assert": "jsr:@std/assert@0.226.0", "@std/http": "jsr:@std/http@0.224.3" } diff --git a/deno.lock b/deno.lock index 1217247..6587d7c 100644 --- a/deno.lock +++ b/deno.lock @@ -24,7 +24,6 @@ "jsr:@std/media-types@0.222": "jsr:@std/media-types@0.222.1", "jsr:@std/media-types@0.223": "jsr:@std/media-types@0.223.0", "jsr:@std/path@0.223": "jsr:@std/path@0.223.0", - "npm:@types/node": "npm:@types/node@18.16.19", "npm:path-to-regexp@6.2.1": "npm:path-to-regexp@6.2.1" }, "jsr": { @@ -128,17 +127,30 @@ } }, "npm": { - "@types/node@18.16.19": { - "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", - "dependencies": {} - }, "path-to-regexp@6.2.1": { "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", "dependencies": {} } } }, - "remote": {}, + "remote": { + "https://esm.sh/@octokit/rest@20.1.1": "56f756c9b39dd8e185fe320a223e3d773683841c17ca2cb234f11ca7c0803cef", + "https://esm.sh/v135/@octokit/auth-token@4.0.0/denonext/auth-token.mjs": "ab9285959f2bc3ca7942bfb81df3e2f15f5895c829455156647faa403b75703b", + "https://esm.sh/v135/@octokit/core@5.2.0/denonext/core.mjs": "2bb7da944dbebeed79f1d2fe6afee247dc9f733b66e6242092268767afa6d571", + "https://esm.sh/v135/@octokit/endpoint@9.0.4/denonext/endpoint.mjs": "d6a3b9d471d2ff2d1fb5b736ebba9589bfcadc0a563ec4e51adc423fdb2e91d2", + "https://esm.sh/v135/@octokit/graphql@7.1.0/denonext/graphql.mjs": "4e0ba99cae80d01f7ec5ed6ada2304d799dc365e6f4094604167b621ff7d0d3f", + "https://esm.sh/v135/@octokit/plugin-paginate-rest@11.3.1/denonext/plugin-paginate-rest.mjs": "8f64c07d869b1a3cacb34a1c837c963f6f28fbc1045ac2617c654ccac87c7b6b", + "https://esm.sh/v135/@octokit/plugin-request-log@4.0.1/denonext/plugin-request-log.mjs": "659edfc1fceba675331ed830ccf523e45529682722fe73781090235834329240", + "https://esm.sh/v135/@octokit/plugin-rest-endpoint-methods@13.2.2/denonext/plugin-rest-endpoint-methods.mjs": "c2795d2650bbf8307c6e58f157954be29986164c694beba8c0f29a9523659748", + "https://esm.sh/v135/@octokit/request-error@5.1.0/denonext/request-error.mjs": "0d7d746d259a2be24a0cd3a3f64eefa3d756f13b873a9abcf41423d2bdec1af2", + "https://esm.sh/v135/@octokit/request@8.4.0/denonext/request.mjs": "a1f63775a6c089a6b286b6e833643b3f253a532b1d538bf6dfd7b3e9396072c3", + "https://esm.sh/v135/@octokit/rest@20.1.1/denonext/rest.mjs": "4ef1a8b66120b3ebe351508fe6bc55aabfad405ab7b7e26232fe831b289580cc", + "https://esm.sh/v135/before-after-hook@2.2.3/denonext/before-after-hook.mjs": "f4262d059d899d7fcaa8d903bcf352df38ac1c040bb45273d79de200ffdad267", + "https://esm.sh/v135/deprecation@2.3.1/denonext/deprecation.mjs": "0bf7139d1068345709e59dddb4daea315691d290a8c896a6e076dea02dd66eaf", + "https://esm.sh/v135/once@1.4.0/denonext/once.mjs": "5ab9193c36609f6c5cf6f1e2e37dd952c87d8ff0b0267433c29b228a2c470cd5", + "https://esm.sh/v135/universal-user-agent@6.0.1/denonext/universal-user-agent.mjs": "053324bcacbaf066d185d6bf0a3797ee474d975f720ce75351238e420fc82c22", + "https://esm.sh/v135/wrappy@1.0.2/denonext/wrappy.mjs": "3c31e4782e0307cf56b319fcec6110f925dafe6cb47a8fa23350d480f5fa8b06" + }, "workspace": { "dependencies": [ "jsr:@oak/oak@16.0.0", diff --git a/src/libs/content.ts b/src/libs/content.ts new file mode 100644 index 0000000..87d8e0e --- /dev/null +++ b/src/libs/content.ts @@ -0,0 +1,26 @@ +import type { RouterContext } from "@oak/oak"; +import { Octokit } from "@octokit/rest"; + +export async function getContent( + ctx: RouterContext, + owner: string, + name: string, + path: string +): Promise { + const octokit = new Octokit(); + + try { + const { status, data } = await octokit.rest.repos.getContent({ + mediaType: { format: "raw" }, + owner: owner, + repo: name, + path: path, + }); + + ctx.response.status = status; + ctx.response.body = data; + } catch (error) { + ctx.response.status = error.status; + ctx.response.body = `โš ๏ธ ${ctx.response.status}: ${error.message}`; + } +} diff --git a/src/libs/mod.ts b/src/libs/mod.ts new file mode 100644 index 0000000..16b396b --- /dev/null +++ b/src/libs/mod.ts @@ -0,0 +1 @@ +export * from "./content.ts"; diff --git a/src/router.ts b/src/router.ts index e429b81..dcdc6cb 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,9 +1,8 @@ import { Router, type RouterContext } from "@oak/oak"; -import { STATUS_CODE } from "@std/http/status"; +import { getContent } from "./libs/mod.ts"; export const router = new Router(); -router.get("/", (ctx: RouterContext) => { +router.get("/", async (ctx: RouterContext) => { ctx.response.type = "text/plain"; - ctx.response.status = STATUS_CODE.OK; - ctx.response.body = "Hello, Deno!"; + await getContent(ctx, "denoland", "deno", "README.md"); }); diff --git a/test/libs/content_test.ts b/test/libs/content_test.ts new file mode 100644 index 0000000..6a756a8 --- /dev/null +++ b/test/libs/content_test.ts @@ -0,0 +1,25 @@ +import { testing, type RouterContext } from "@oak/oak"; +import { assertEquals, assertStringIncludes } from "@std/assert"; +import { STATUS_CODE } from "@std/http/status"; +import { getContent } from "../../src/libs/content.ts"; + +Deno.test("Get Content", async () => { + const ctx: RouterContext = testing.createMockContext(); + await getContent(ctx, "denoland", "deno", "README.md"); + assertEquals(ctx.response.status, STATUS_CODE.OK); +}); + +Deno.test("Get Content (Not found)", async () => { + const ctx: RouterContext = testing.createMockContext(); + await getContent( + ctx, + "unknown-owner", + "unknown-repo", + "unknown-path/to/file" + ); + assertEquals(ctx.response.status, STATUS_CODE.NotFound); + assertStringIncludes( + ctx.response.body!.toString(), + `โš ๏ธ ${STATUS_CODE.NotFound}:` + ); +}); diff --git a/test/router_test.ts b/test/router_test.ts index add0567..1b0e902 100644 --- a/test/router_test.ts +++ b/test/router_test.ts @@ -10,6 +10,5 @@ Deno.test("Root Path", async () => { }); await router.routes()(ctx, () => Promise.resolve()); - assertEquals(ctx.response.body, "Hello, Deno!"); assertEquals(ctx.response.status, STATUS_CODE.OK); }); From 7192604c2f16032fce3ebc7e66e82932d271294d Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:09:33 +0900 Subject: [PATCH 04/19] feat(env): Specify repository info via environment variables It makes users to set their prefered file. --- .env.template | 3 +++ .gitignore | 1 + deno.json | 4 ++-- src/libs/content.ts | 13 ++++++------- src/libs/env.ts | 15 +++++++++++++++ src/router.ts | 2 +- src/types/mod.ts | 1 + src/types/repository.ts | 5 +++++ test/libs/content_test.ts | 28 +++++++++++++++++++++------- test/libs/env_test.ts | 27 +++++++++++++++++++++++++++ test/router_test.ts | 9 +++++++++ 11 files changed, 91 insertions(+), 17 deletions(-) create mode 100644 .env.template create mode 100644 .gitignore create mode 100644 src/libs/env.ts create mode 100644 src/types/mod.ts create mode 100644 src/types/repository.ts create mode 100644 test/libs/env_test.ts diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..e7fd7b1 --- /dev/null +++ b/.env.template @@ -0,0 +1,3 @@ +REPOSITORY_OWNER= +REPOSITORY_NAME= +REPOSITORY_PATH= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f10862a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.env diff --git a/deno.json b/deno.json index acb859b..722df3d 100644 --- a/deno.json +++ b/deno.json @@ -3,8 +3,8 @@ "fmt": { "exclude": ["LICENSE", ".github/**/*.md"] }, "test": { "include": ["src/", "test/"] }, "tasks": { - "start": "deno run --allow-net='0.0.0.0,api.github.com' ./src/server.ts", - "test": "deno test --allow-net='api.github.com' --parallel --shuffle" + "start": "deno run --env='.env' --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH' --allow-net='0.0.0.0,api.github.com' ./src/server.ts", + "test": "deno test --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH' --allow-net='api.github.com' --parallel --shuffle" }, "imports": { "@oak/oak": "jsr:@oak/oak@16.0.0", diff --git a/src/libs/content.ts b/src/libs/content.ts index 87d8e0e..e262381 100644 --- a/src/libs/content.ts +++ b/src/libs/content.ts @@ -1,20 +1,19 @@ import type { RouterContext } from "@oak/oak"; import { Octokit } from "@octokit/rest"; +import { getRepository } from "./env.ts"; export async function getContent( - ctx: RouterContext, - owner: string, - name: string, - path: string + ctx: RouterContext ): Promise { const octokit = new Octokit(); + const repository = getRepository(); try { const { status, data } = await octokit.rest.repos.getContent({ mediaType: { format: "raw" }, - owner: owner, - repo: name, - path: path, + owner: repository.owner, + repo: repository.name, + path: repository.path, }); ctx.response.status = status; diff --git a/src/libs/env.ts b/src/libs/env.ts new file mode 100644 index 0000000..525ef8c --- /dev/null +++ b/src/libs/env.ts @@ -0,0 +1,15 @@ +import type { Repository } from "../types/mod.ts"; + +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(`๐Ÿšจ Env not set: $REPOSITORY_${key.toUpperCase()}`); + + return repository; +} diff --git a/src/router.ts b/src/router.ts index dcdc6cb..461084b 100644 --- a/src/router.ts +++ b/src/router.ts @@ -4,5 +4,5 @@ import { getContent } from "./libs/mod.ts"; export const router = new Router(); router.get("/", async (ctx: RouterContext) => { ctx.response.type = "text/plain"; - await getContent(ctx, "denoland", "deno", "README.md"); + await getContent(ctx); }); diff --git a/src/types/mod.ts b/src/types/mod.ts new file mode 100644 index 0000000..94b8c91 --- /dev/null +++ b/src/types/mod.ts @@ -0,0 +1 @@ +export * from "./repository.ts"; diff --git a/src/types/repository.ts b/src/types/repository.ts new file mode 100644 index 0000000..d2dfbcf --- /dev/null +++ b/src/types/repository.ts @@ -0,0 +1,5 @@ +export type Repository = { + owner: string; + name: string; + path: string; +}; diff --git a/test/libs/content_test.ts b/test/libs/content_test.ts index 6a756a8..607f60a 100644 --- a/test/libs/content_test.ts +++ b/test/libs/content_test.ts @@ -2,21 +2,35 @@ import { testing, type RouterContext } from "@oak/oak"; import { assertEquals, assertStringIncludes } from "@std/assert"; import { STATUS_CODE } from "@std/http/status"; import { getContent } from "../../src/libs/content.ts"; +import { Repository } from "../../src/types/mod.ts"; Deno.test("Get Content", async () => { const ctx: RouterContext = testing.createMockContext(); - await getContent(ctx, "denoland", "deno", "README.md"); + const repository: Repository = { + owner: "denoland", + name: "deno", + path: "README.md", + }; + Deno.env.set("REPOSITORY_OWNER", repository.owner); + Deno.env.set("REPOSITORY_NAME", repository.name); + Deno.env.set("REPOSITORY_PATH", repository.path); + + await getContent(ctx); assertEquals(ctx.response.status, STATUS_CODE.OK); }); Deno.test("Get Content (Not found)", async () => { const ctx: RouterContext = testing.createMockContext(); - await getContent( - ctx, - "unknown-owner", - "unknown-repo", - "unknown-path/to/file" - ); + const repository: Repository = { + owner: "unknown-owner", + name: "unknown-repo", + path: "unknown-path/to/file", + }; + Deno.env.set("REPOSITORY_OWNER", repository.owner); + Deno.env.set("REPOSITORY_NAME", repository.name); + Deno.env.set("REPOSITORY_PATH", repository.path); + + await getContent(ctx); assertEquals(ctx.response.status, STATUS_CODE.NotFound); assertStringIncludes( ctx.response.body!.toString(), diff --git a/test/libs/env_test.ts b/test/libs/env_test.ts new file mode 100644 index 0000000..8c46b01 --- /dev/null +++ b/test/libs/env_test.ts @@ -0,0 +1,27 @@ +import { assertEquals } from "@std/assert"; +import { getRepository } from "../../src/libs/env.ts"; + +Deno.test("Get Repository Env", () => { + const repositoryOwner = "repository-owner"; + const repositoryName = "repository-name"; + const repositoryPath = "repository-path"; + Deno.env.set("REPOSITORY_OWNER", repositoryOwner); + Deno.env.set("REPOSITORY_NAME", repositoryName); + Deno.env.set("REPOSITORY_PATH", repositoryPath); + + const repository = getRepository(); + assertEquals(repository.owner, repositoryOwner); + assertEquals(repository.name, repositoryName); + assertEquals(repository.path, repositoryPath); +}); + +Deno.test("Get Repository Env (Not set)", () => { + Deno.env.delete("REPOSITORY_OWNER"); + Deno.env.delete("REPOSITORY_NAME"); + Deno.env.delete("REPOSITORY_PATH"); + + const repository = getRepository(); + assertEquals(repository.owner, ""); + assertEquals(repository.name, ""); + assertEquals(repository.path, ""); +}); diff --git a/test/router_test.ts b/test/router_test.ts index 1b0e902..c93ba26 100644 --- a/test/router_test.ts +++ b/test/router_test.ts @@ -2,12 +2,21 @@ import { testing, type RouterContext } from "@oak/oak"; import { assertEquals } from "@std/assert"; import { STATUS_CODE } from "@std/http/status"; import { router } from "../src/router.ts"; +import { Repository } from "../src/types/mod.ts"; Deno.test("Root Path", async () => { const ctx: RouterContext = testing.createMockContext({ method: "GET", path: "/", }); + const repository: Repository = { + owner: "denoland", + name: "deno", + path: "README.md", + }; + Deno.env.set("REPOSITORY_OWNER", repository.owner); + Deno.env.set("REPOSITORY_NAME", repository.name); + Deno.env.set("REPOSITORY_PATH", repository.path); await router.routes()(ctx, () => Promise.resolve()); assertEquals(ctx.response.status, STATUS_CODE.OK); From 3b07fa4c029a8a580bbd343cdc06b905d9214d5b Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:14:56 +0900 Subject: [PATCH 05/19] feat(redirect): Redirect for browsers When accessed from browsers, redirect to the GitHub page. --- deno.json | 4 +++- deno.lock | 15 +++++++++++---- src/libs/mod.ts | 1 + src/libs/redirect.ts | 27 +++++++++++++++++++++++++++ src/router.ts | 3 ++- test/libs/redirect_test.ts | 28 ++++++++++++++++++++++++++++ 6 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 src/libs/redirect.ts create mode 100644 test/libs/redirect_test.ts diff --git a/deno.json b/deno.json index 722df3d..d969afc 100644 --- a/deno.json +++ b/deno.json @@ -10,6 +10,8 @@ "@oak/oak": "jsr:@oak/oak@16.0.0", "@octokit/rest": "https://esm.sh/@octokit/rest@20.1.1", "@std/assert": "jsr:@std/assert@0.226.0", - "@std/http": "jsr:@std/http@0.224.3" + "@std/http": "jsr:@std/http@0.224.3", + "@std/http/user-agent": "jsr:@std/http@~0.223/user-agent", + "@std/path": "jsr:@std/path@0.225.2" } } diff --git a/deno.lock b/deno.lock index 6587d7c..539d2b9 100644 --- a/deno.lock +++ b/deno.lock @@ -9,6 +9,7 @@ "jsr:@std/assert@0.226.0": "jsr:@std/assert@0.226.0", "jsr:@std/assert@^0.222.1": "jsr:@std/assert@0.222.1", "jsr:@std/assert@^0.223.0": "jsr:@std/assert@0.223.0", + "jsr:@std/assert@^0.226.0": "jsr:@std/assert@0.226.0", "jsr:@std/bytes@0.222": "jsr:@std/bytes@0.222.1", "jsr:@std/bytes@0.223": "jsr:@std/bytes@0.223.0", "jsr:@std/bytes@^0.223.0": "jsr:@std/bytes@0.223.0", @@ -19,11 +20,13 @@ "jsr:@std/http@0.222": "jsr:@std/http@0.222.1", "jsr:@std/http@0.223": "jsr:@std/http@0.223.0", "jsr:@std/http@0.224.3": "jsr:@std/http@0.224.3", + "jsr:@std/http@~0.223": "jsr:@std/http@0.223.0", "jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.0", "jsr:@std/io@0.223": "jsr:@std/io@0.223.0", "jsr:@std/media-types@0.222": "jsr:@std/media-types@0.222.1", "jsr:@std/media-types@0.223": "jsr:@std/media-types@0.223.0", "jsr:@std/path@0.223": "jsr:@std/path@0.223.0", + "jsr:@std/path@0.225.2": "jsr:@std/path@0.225.2", "npm:path-to-regexp@6.2.1": "npm:path-to-regexp@6.2.1" }, "jsr": { @@ -78,7 +81,6 @@ "@std/crypto@0.223.0": { "integrity": "1aa9555ff56b09e197ad988ea200f84bc6781fd4fd83f3a156ee44449af93000", "dependencies": [ - "jsr:@std/assert@^0.223.0", "jsr:@std/encoding@^0.223.0" ] }, @@ -120,9 +122,12 @@ "integrity": "84684680c2eb6bc6d9369c6d6f26a49decaf2c7603ff531862dda575d9d6776e" }, "@std/path@0.223.0": { - "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989", + "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989" + }, + "@std/path@0.225.2": { + "integrity": "0f2db41d36b50ef048dcb0399aac720a5348638dd3cb5bf80685bf2a745aa506", "dependencies": [ - "jsr:@std/assert@^0.223.0" + "jsr:@std/assert@^0.226.0" ] } }, @@ -155,7 +160,9 @@ "dependencies": [ "jsr:@oak/oak@16.0.0", "jsr:@std/assert@0.226.0", - "jsr:@std/http@0.224.3" + "jsr:@std/http@0.224.3", + "jsr:@std/http@~0.223", + "jsr:@std/path@0.225.2" ] } } diff --git a/src/libs/mod.ts b/src/libs/mod.ts index 16b396b..7311f3e 100644 --- a/src/libs/mod.ts +++ b/src/libs/mod.ts @@ -1 +1,2 @@ export * from "./content.ts"; +export * from "./redirect.ts"; diff --git a/src/libs/redirect.ts b/src/libs/redirect.ts new file mode 100644 index 0000000..59d25ea --- /dev/null +++ b/src/libs/redirect.ts @@ -0,0 +1,27 @@ +import type { RouterContext } from "@oak/oak"; +import { STATUS_CODE } from "@std/http/status"; +import { UserAgent } from "@std/http/user-agent"; +import { join } from "@std/path"; +import { getRepository } from "./env.ts"; + +export function redirect( + ctx: RouterContext, + userAgent: UserAgent +): void { + const repository = getRepository(); + const url: string = + "https://" + + join( + "github.com", + repository.owner, + repository.name, + "blob", + "HEAD", + repository.path + ); + + if (userAgent?.browser.name) { + ctx.response.status = STATUS_CODE.PermanentRedirect; + ctx.response.redirect(url); + } +} diff --git a/src/router.ts b/src/router.ts index 461084b..df3ce56 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,8 +1,9 @@ import { Router, type RouterContext } from "@oak/oak"; -import { getContent } from "./libs/mod.ts"; +import { getContent, redirect } from "./libs/mod.ts"; export const router = new Router(); router.get("/", async (ctx: RouterContext) => { ctx.response.type = "text/plain"; await getContent(ctx); + redirect(ctx, ctx.request.userAgent); }); diff --git a/test/libs/redirect_test.ts b/test/libs/redirect_test.ts new file mode 100644 index 0000000..5d48e66 --- /dev/null +++ b/test/libs/redirect_test.ts @@ -0,0 +1,28 @@ +import { testing, type RouterContext } from "@oak/oak"; +import { assertEquals } from "@std/assert"; +import { STATUS_CODE } from "@std/http/status"; +import { UserAgent } from "@std/http/user-agent"; +import { redirect } from "../../src/libs/redirect.ts"; +import { Repository } from "../../src/types/repository.ts"; + +Deno.test("Redirect Detection", () => { + const ctx: RouterContext = testing.createMockContext({ + method: "GET", + path: "/", + }); + const repository: Repository = { + owner: "repository-owner", + name: "repository-name", + path: "repository-path", + }; + Deno.env.set("REPOSITORY_OWNER", repository.owner); + Deno.env.set("REPOSITORY_NAME", repository.name); + Deno.env.set("REPOSITORY_PATH", repository.path); + + redirect(ctx, new UserAgent("Chrome/1.2.3")); + assertEquals(ctx.response.status, STATUS_CODE.PermanentRedirect); + assertEquals( + ctx.response.headers.get("location"), + "https://github.com/repository-owner/repository-name/blob/HEAD/repository-path" + ); +}); From 0fa6e4f3ce2395bf8c13314f4d2020398249c5a5 Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:25:06 +0900 Subject: [PATCH 06/19] feat(router): Allow sub directory for ref This makes it possible to access files on different branch or tag. --- src/libs/content.ts | 4 +++- src/libs/redirect.ts | 5 +++-- src/router.ts | 17 ++++++++++++----- test/libs/content_test.ts | 15 +++++++++++++++ test/libs/redirect_test.ts | 24 ++++++++++++++++++++---- test/router_test.ts | 20 +++++++++++++++++++- 6 files changed, 72 insertions(+), 13 deletions(-) diff --git a/src/libs/content.ts b/src/libs/content.ts index e262381..080dada 100644 --- a/src/libs/content.ts +++ b/src/libs/content.ts @@ -3,7 +3,8 @@ import { Octokit } from "@octokit/rest"; import { getRepository } from "./env.ts"; export async function getContent( - ctx: RouterContext + ctx: RouterContext, + ref: string | undefined = undefined ): Promise { const octokit = new Octokit(); const repository = getRepository(); @@ -14,6 +15,7 @@ export async function getContent( owner: repository.owner, repo: repository.name, path: repository.path, + ref: ref, }); ctx.response.status = status; diff --git a/src/libs/redirect.ts b/src/libs/redirect.ts index 59d25ea..951b374 100644 --- a/src/libs/redirect.ts +++ b/src/libs/redirect.ts @@ -6,7 +6,8 @@ import { getRepository } from "./env.ts"; export function redirect( ctx: RouterContext, - userAgent: UserAgent + userAgent: UserAgent, + ref: string = "HEAD" ): void { const repository = getRepository(); const url: string = @@ -16,7 +17,7 @@ export function redirect( repository.owner, repository.name, "blob", - "HEAD", + ref, repository.path ); diff --git a/src/router.ts b/src/router.ts index df3ce56..0664a00 100644 --- a/src/router.ts +++ b/src/router.ts @@ -2,8 +2,15 @@ import { Router, type RouterContext } from "@oak/oak"; import { getContent, redirect } from "./libs/mod.ts"; export const router = new Router(); -router.get("/", async (ctx: RouterContext) => { - ctx.response.type = "text/plain"; - await getContent(ctx); - redirect(ctx, ctx.request.userAgent); -}); +router + .get("/", async (ctx: RouterContext) => { + ctx.response.type = "text/plain"; + await getContent(ctx); + redirect(ctx, ctx.request.userAgent); + }) + .get("/:ref", async (ctx: RouterContext) => { + const ref: string | undefined = ctx.params.ref; + ctx.response.type = "text/plain"; + await getContent(ctx, ref); + redirect(ctx, ctx.request.userAgent, ref); + }); diff --git a/test/libs/content_test.ts b/test/libs/content_test.ts index 607f60a..c8c68e8 100644 --- a/test/libs/content_test.ts +++ b/test/libs/content_test.ts @@ -19,6 +19,21 @@ Deno.test("Get Content", async () => { assertEquals(ctx.response.status, STATUS_CODE.OK); }); +Deno.test("Get Content (With ref)", async () => { + const ctx: RouterContext = testing.createMockContext(); + const repository: Repository = { + owner: "denoland", + name: "deno", + path: "README.md", + }; + Deno.env.set("REPOSITORY_OWNER", repository.owner); + Deno.env.set("REPOSITORY_NAME", repository.name); + Deno.env.set("REPOSITORY_PATH", repository.path); + + await getContent(ctx, "v1.0.0"); + assertEquals(ctx.response.status, STATUS_CODE.OK); +}); + Deno.test("Get Content (Not found)", async () => { const ctx: RouterContext = testing.createMockContext(); const repository: Repository = { diff --git a/test/libs/redirect_test.ts b/test/libs/redirect_test.ts index 5d48e66..6576246 100644 --- a/test/libs/redirect_test.ts +++ b/test/libs/redirect_test.ts @@ -6,10 +6,7 @@ import { redirect } from "../../src/libs/redirect.ts"; import { Repository } from "../../src/types/repository.ts"; Deno.test("Redirect Detection", () => { - const ctx: RouterContext = testing.createMockContext({ - method: "GET", - path: "/", - }); + const ctx: RouterContext = testing.createMockContext(); const repository: Repository = { owner: "repository-owner", name: "repository-name", @@ -26,3 +23,22 @@ Deno.test("Redirect Detection", () => { "https://github.com/repository-owner/repository-name/blob/HEAD/repository-path" ); }); + +Deno.test("Redirect Detection (With ref)", () => { + const ctx: RouterContext = testing.createMockContext(); + const repository: Repository = { + owner: "repository-owner", + name: "repository-name", + path: "repository-path", + }; + Deno.env.set("REPOSITORY_OWNER", repository.owner); + Deno.env.set("REPOSITORY_NAME", repository.name); + Deno.env.set("REPOSITORY_PATH", repository.path); + + redirect(ctx, new UserAgent("Chrome/1.2.3"), "v1.0.0"); + assertEquals(ctx.response.status, STATUS_CODE.PermanentRedirect); + assertEquals( + ctx.response.headers.get("location"), + "https://github.com/repository-owner/repository-name/blob/v1.0.0/repository-path" + ); +}); diff --git a/test/router_test.ts b/test/router_test.ts index c93ba26..7a02637 100644 --- a/test/router_test.ts +++ b/test/router_test.ts @@ -4,7 +4,7 @@ import { STATUS_CODE } from "@std/http/status"; import { router } from "../src/router.ts"; import { Repository } from "../src/types/mod.ts"; -Deno.test("Root Path", async () => { +Deno.test("Serve (/)", async () => { const ctx: RouterContext = testing.createMockContext({ method: "GET", path: "/", @@ -21,3 +21,21 @@ Deno.test("Root Path", async () => { assertEquals(ctx.response.status, STATUS_CODE.OK); }); + +Deno.test("Serve (/:ref)", async () => { + const ctx: RouterContext = testing.createMockContext({ + method: "GET", + path: "/v1.0.0", + }); + const repository: Repository = { + owner: "denoland", + name: "deno", + path: "README.md", + }; + Deno.env.set("REPOSITORY_OWNER", repository.owner); + Deno.env.set("REPOSITORY_NAME", repository.name); + Deno.env.set("REPOSITORY_PATH", repository.path); + await router.routes()(ctx, () => Promise.resolve()); + + assertEquals(ctx.response.status, STATUS_CODE.OK); +}); From 42c072c3ec1ce9640bfc951fe0a644a0adac2fda Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:42:56 +0900 Subject: [PATCH 07/19] test(util): Pull out test variables or a function To keep code clean, and make it easy to change the ingredients. --- test/libs/content_test.ts | 31 +++++-------------------------- test/libs/env_test.ts | 14 +++++--------- test/libs/redirect_test.ts | 26 ++++++-------------------- test/router_test.ts | 22 ++++------------------ test/utils.ts | 21 +++++++++++++++++++++ 5 files changed, 41 insertions(+), 73 deletions(-) create mode 100644 test/utils.ts diff --git a/test/libs/content_test.ts b/test/libs/content_test.ts index c8c68e8..05e091b 100644 --- a/test/libs/content_test.ts +++ b/test/libs/content_test.ts @@ -2,18 +2,11 @@ import { testing, type RouterContext } from "@oak/oak"; import { assertEquals, assertStringIncludes } from "@std/assert"; import { STATUS_CODE } from "@std/http/status"; import { getContent } from "../../src/libs/content.ts"; -import { Repository } from "../../src/types/mod.ts"; +import { testRepo, unknownRepo, testRef, exportRepo } from "../utils.ts"; Deno.test("Get Content", async () => { const ctx: RouterContext = testing.createMockContext(); - const repository: Repository = { - owner: "denoland", - name: "deno", - path: "README.md", - }; - Deno.env.set("REPOSITORY_OWNER", repository.owner); - Deno.env.set("REPOSITORY_NAME", repository.name); - Deno.env.set("REPOSITORY_PATH", repository.path); + exportRepo(testRepo); await getContent(ctx); assertEquals(ctx.response.status, STATUS_CODE.OK); @@ -21,29 +14,15 @@ Deno.test("Get Content", async () => { Deno.test("Get Content (With ref)", async () => { const ctx: RouterContext = testing.createMockContext(); - const repository: Repository = { - owner: "denoland", - name: "deno", - path: "README.md", - }; - Deno.env.set("REPOSITORY_OWNER", repository.owner); - Deno.env.set("REPOSITORY_NAME", repository.name); - Deno.env.set("REPOSITORY_PATH", repository.path); + exportRepo(testRepo); - await getContent(ctx, "v1.0.0"); + await getContent(ctx, testRef); assertEquals(ctx.response.status, STATUS_CODE.OK); }); Deno.test("Get Content (Not found)", async () => { const ctx: RouterContext = testing.createMockContext(); - const repository: Repository = { - owner: "unknown-owner", - name: "unknown-repo", - path: "unknown-path/to/file", - }; - Deno.env.set("REPOSITORY_OWNER", repository.owner); - Deno.env.set("REPOSITORY_NAME", repository.name); - Deno.env.set("REPOSITORY_PATH", repository.path); + exportRepo(unknownRepo); await getContent(ctx); assertEquals(ctx.response.status, STATUS_CODE.NotFound); diff --git a/test/libs/env_test.ts b/test/libs/env_test.ts index 8c46b01..996b7cd 100644 --- a/test/libs/env_test.ts +++ b/test/libs/env_test.ts @@ -1,18 +1,14 @@ import { assertEquals } from "@std/assert"; import { getRepository } from "../../src/libs/env.ts"; +import { unknownRepo, exportRepo } from "../utils.ts"; Deno.test("Get Repository Env", () => { - const repositoryOwner = "repository-owner"; - const repositoryName = "repository-name"; - const repositoryPath = "repository-path"; - Deno.env.set("REPOSITORY_OWNER", repositoryOwner); - Deno.env.set("REPOSITORY_NAME", repositoryName); - Deno.env.set("REPOSITORY_PATH", repositoryPath); + exportRepo(unknownRepo); const repository = getRepository(); - assertEquals(repository.owner, repositoryOwner); - assertEquals(repository.name, repositoryName); - assertEquals(repository.path, repositoryPath); + assertEquals(repository.owner, unknownRepo.owner); + assertEquals(repository.name, unknownRepo.name); + assertEquals(repository.path, unknownRepo.path); }); Deno.test("Get Repository Env (Not set)", () => { diff --git a/test/libs/redirect_test.ts b/test/libs/redirect_test.ts index 6576246..588a565 100644 --- a/test/libs/redirect_test.ts +++ b/test/libs/redirect_test.ts @@ -3,42 +3,28 @@ import { assertEquals } from "@std/assert"; import { STATUS_CODE } from "@std/http/status"; import { UserAgent } from "@std/http/user-agent"; import { redirect } from "../../src/libs/redirect.ts"; -import { Repository } from "../../src/types/repository.ts"; +import { unknownRepo, testRef, exportRepo } from "../utils.ts"; Deno.test("Redirect Detection", () => { const ctx: RouterContext = testing.createMockContext(); - const repository: Repository = { - owner: "repository-owner", - name: "repository-name", - path: "repository-path", - }; - Deno.env.set("REPOSITORY_OWNER", repository.owner); - Deno.env.set("REPOSITORY_NAME", repository.name); - Deno.env.set("REPOSITORY_PATH", repository.path); + exportRepo(unknownRepo); redirect(ctx, new UserAgent("Chrome/1.2.3")); assertEquals(ctx.response.status, STATUS_CODE.PermanentRedirect); assertEquals( ctx.response.headers.get("location"), - "https://github.com/repository-owner/repository-name/blob/HEAD/repository-path" + `https://github.com/${unknownRepo.owner}/${unknownRepo.name}/blob/HEAD/${unknownRepo.path}` ); }); Deno.test("Redirect Detection (With ref)", () => { const ctx: RouterContext = testing.createMockContext(); - const repository: Repository = { - owner: "repository-owner", - name: "repository-name", - path: "repository-path", - }; - Deno.env.set("REPOSITORY_OWNER", repository.owner); - Deno.env.set("REPOSITORY_NAME", repository.name); - Deno.env.set("REPOSITORY_PATH", repository.path); + exportRepo(unknownRepo); - redirect(ctx, new UserAgent("Chrome/1.2.3"), "v1.0.0"); + redirect(ctx, new UserAgent("Chrome/1.2.3"), testRef); assertEquals(ctx.response.status, STATUS_CODE.PermanentRedirect); assertEquals( ctx.response.headers.get("location"), - "https://github.com/repository-owner/repository-name/blob/v1.0.0/repository-path" + `https://github.com/${unknownRepo.owner}/${unknownRepo.name}/blob/${testRef}/${unknownRepo.path}` ); }); diff --git a/test/router_test.ts b/test/router_test.ts index 7a02637..59a8262 100644 --- a/test/router_test.ts +++ b/test/router_test.ts @@ -2,21 +2,14 @@ import { testing, type RouterContext } from "@oak/oak"; import { assertEquals } from "@std/assert"; import { STATUS_CODE } from "@std/http/status"; import { router } from "../src/router.ts"; -import { Repository } from "../src/types/mod.ts"; +import { testRepo, testRef, exportRepo } from "./utils.ts"; Deno.test("Serve (/)", async () => { const ctx: RouterContext = testing.createMockContext({ method: "GET", path: "/", }); - const repository: Repository = { - owner: "denoland", - name: "deno", - path: "README.md", - }; - Deno.env.set("REPOSITORY_OWNER", repository.owner); - Deno.env.set("REPOSITORY_NAME", repository.name); - Deno.env.set("REPOSITORY_PATH", repository.path); + exportRepo(testRepo); await router.routes()(ctx, () => Promise.resolve()); assertEquals(ctx.response.status, STATUS_CODE.OK); @@ -25,16 +18,9 @@ Deno.test("Serve (/)", async () => { Deno.test("Serve (/:ref)", async () => { const ctx: RouterContext = testing.createMockContext({ method: "GET", - path: "/v1.0.0", + path: `/${testRef}`, }); - const repository: Repository = { - owner: "denoland", - name: "deno", - path: "README.md", - }; - Deno.env.set("REPOSITORY_OWNER", repository.owner); - Deno.env.set("REPOSITORY_NAME", repository.name); - Deno.env.set("REPOSITORY_PATH", repository.path); + exportRepo(testRepo); await router.routes()(ctx, () => Promise.resolve()); assertEquals(ctx.response.status, STATUS_CODE.OK); diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 0000000..b576b13 --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,21 @@ +import type { Repository } from "../src/types/mod.ts"; + +export const testRepo: Repository = { + owner: "denoland", + name: "deno", + path: "README.md", +}; + +export const unknownRepo: Repository = { + owner: "unknown-owner", + name: "unknown-repo", + path: "unknown-path", +}; + +export const testRef = "v1.0.0"; + +export function exportRepo(repository: Repository) { + Deno.env.set("REPOSITORY_OWNER", repository.owner); + Deno.env.set("REPOSITORY_NAME", repository.name); + Deno.env.set("REPOSITORY_PATH", repository.path); +} From 86fab9542f83043f69a8122fd958d58af291361c Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:49:02 +0900 Subject: [PATCH 08/19] chore(dev): Useful dev command and messages Run watcher process, and show the message for hosting address. --- deno.json | 5 ++++- deno.lock | 5 +++++ src/libs/env.ts | 2 +- src/server.ts | 13 +++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/deno.json b/deno.json index d969afc..fa445be 100644 --- a/deno.json +++ b/deno.json @@ -3,13 +3,16 @@ "fmt": { "exclude": ["LICENSE", ".github/**/*.md"] }, "test": { "include": ["src/", "test/"] }, "tasks": { - "start": "deno run --env='.env' --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH' --allow-net='0.0.0.0,api.github.com' ./src/server.ts", + "run": "deno run --env='.env' --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH' --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' --allow-net='api.github.com' --parallel --shuffle" }, "imports": { "@oak/oak": "jsr:@oak/oak@16.0.0", "@octokit/rest": "https://esm.sh/@octokit/rest@20.1.1", "@std/assert": "jsr:@std/assert@0.226.0", + "@std/fmt": "jsr:@std/fmt@0.225.3", "@std/http": "jsr:@std/http@0.224.3", "@std/http/user-agent": "jsr:@std/http@~0.223/user-agent", "@std/path": "jsr:@std/path@0.225.2" diff --git a/deno.lock b/deno.lock index 539d2b9..52911b8 100644 --- a/deno.lock +++ b/deno.lock @@ -17,6 +17,7 @@ "jsr:@std/crypto@0.223": "jsr:@std/crypto@0.223.0", "jsr:@std/encoding@^0.222.1": "jsr:@std/encoding@0.222.1", "jsr:@std/encoding@^0.223.0": "jsr:@std/encoding@0.223.0", + "jsr:@std/fmt@0.225.3": "jsr:@std/fmt@0.225.3", "jsr:@std/http@0.222": "jsr:@std/http@0.222.1", "jsr:@std/http@0.223": "jsr:@std/http@0.223.0", "jsr:@std/http@0.224.3": "jsr:@std/http@0.224.3", @@ -90,6 +91,9 @@ "@std/encoding@0.223.0": { "integrity": "2b5615a75e00337ce113f34cf2f9b8c18182c751a8dcc8b1a2c2fc0e117bef00" }, + "@std/fmt@0.225.3": { + "integrity": "cb6ea567155f9865b80b502b2dde7671803eddd6dad743d8851d0de2c40bd349" + }, "@std/http@0.222.1": { "integrity": "a3c731ec6040927aa37d8378b9e6b467e2e3068c1f97838a6e79ee3bdc103521", "dependencies": [ @@ -160,6 +164,7 @@ "dependencies": [ "jsr:@oak/oak@16.0.0", "jsr:@std/assert@0.226.0", + "jsr:@std/fmt@0.225.3", "jsr:@std/http@0.224.3", "jsr:@std/http@~0.223", "jsr:@std/path@0.225.2" diff --git a/src/libs/env.ts b/src/libs/env.ts index 525ef8c..cb9c823 100644 --- a/src/libs/env.ts +++ b/src/libs/env.ts @@ -9,7 +9,7 @@ export function getRepository(): Repository { for (const key in repository) if (!repository[key as keyof Repository]) - console.error(`๐Ÿšจ Env not set: $REPOSITORY_${key.toUpperCase()}`); + console.error(`๐Ÿšจ missing: $REPOSITORY_${key.toUpperCase()}`); return repository; } diff --git a/src/server.ts b/src/server.ts index 1144ac9..3760e80 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,4 +1,6 @@ import { Application } from "@oak/oak"; +import type { ApplicationListenEvent } from "@oak/oak/application"; +import { yellow } from "@std/fmt/colors"; import { router } from "./router.ts"; const app = new Application(); @@ -6,4 +8,15 @@ const app = new Application(); app.use(router.routes()); app.use(router.allowedMethods()); +app.addEventListener( + "listen", + ({ secure, hostname, port }: ApplicationListenEvent) => { + console.log( + `๐Ÿ”” listening: ${yellow( + `${secure ? "https" : "http"}://${hostname ?? "localhost"}:${port}` + )}` + ); + } +); + await app.listen({ port: 8080 }); From dc80919a4b1045eb17ef8f92e04791b340296e07 Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:53:35 +0900 Subject: [PATCH 09/19] feat(env): Attach `GITHUB_TOKEN` for GitHub API To get private file, or avoid API limit. --- .env.template | 1 + deno.json | 4 ++-- src/libs/content.ts | 4 ++-- src/libs/env.ts | 2 ++ 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.env.template b/.env.template index e7fd7b1..35b94ee 100644 --- a/.env.template +++ b/.env.template @@ -1,3 +1,4 @@ REPOSITORY_OWNER= REPOSITORY_NAME= REPOSITORY_PATH= +GITHUB_TOKEN= diff --git a/deno.json b/deno.json index fa445be..7e53786 100644 --- a/deno.json +++ b/deno.json @@ -3,10 +3,10 @@ "fmt": { "exclude": ["LICENSE", ".github/**/*.md"] }, "test": { "include": ["src/", "test/"] }, "tasks": { - "run": "deno run --env='.env' --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH' --allow-net='0.0.0.0,api.github.com'", + "run": "deno run --env='.env' --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH,GITHUB_TOKEN' --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' --allow-net='api.github.com' --parallel --shuffle" + "test": "deno test --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH,GITHUB_TOKEN' --allow-net='api.github.com' --parallel --shuffle" }, "imports": { "@oak/oak": "jsr:@oak/oak@16.0.0", diff --git a/src/libs/content.ts b/src/libs/content.ts index 080dada..e9e8022 100644 --- a/src/libs/content.ts +++ b/src/libs/content.ts @@ -1,12 +1,12 @@ import type { RouterContext } from "@oak/oak"; import { Octokit } from "@octokit/rest"; -import { getRepository } from "./env.ts"; +import { getRepository, githubToken } from "./env.ts"; export async function getContent( ctx: RouterContext, ref: string | undefined = undefined ): Promise { - const octokit = new Octokit(); + const octokit = new Octokit({ auth: githubToken }); const repository = getRepository(); try { diff --git a/src/libs/env.ts b/src/libs/env.ts index cb9c823..2bc7b03 100644 --- a/src/libs/env.ts +++ b/src/libs/env.ts @@ -13,3 +13,5 @@ export function getRepository(): Repository { return repository; } + +export const githubToken: string | undefined = Deno.env.get("GITHUB_TOKEN"); From 7245cfb017176cbc9196b0823045b2f64c806939 Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:41:26 +0900 Subject: [PATCH 10/19] docs(manual): Add some docs for users Users can understand what this project is. --- .env.template => .env.tmpl | 0 .github/CODE_OF_CONDUCT.md | 130 +++++++++++++++++++++++++++++++++++ .github/CONTRIBUTING.md | 74 ++++++++++++++++++++ .github/README.md | 137 +++++++++++++++++++++++++++++++++++++ .github/SECURITY.md | 8 +++ LICENSE | 21 ++++++ README.md | 0 7 files changed, 370 insertions(+) rename .env.template => .env.tmpl (100%) create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/README.md create mode 100644 .github/SECURITY.md create mode 100644 LICENSE delete mode 100644 README.md diff --git a/.env.template b/.env.tmpl similarity index 100% rename from .env.template rename to .env.tmpl diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..387d15f --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,130 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +any ways @5ouma have. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html). + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq]. Translations are available at +[https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). + +[https://www.contributor-covenant.org/faq]: https://www.contributor-covenant.org/faq diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..56f89b6 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,74 @@ +# Contributing Guide + +> [!NOTE] +> You must follow the [Code of Conduct](./CODE_OF_CONDUCT.md). + +I happily welcome your contributions! +Before you contribute, +I would recommend reading this guideline for a better development experience. + +
+ +- [๐Ÿ’ฌ Commit Message](#-commit-message) +- [๐ŸŽ‹ Pull Requests Branch](#-pull-requests-branch) +- [โ“ Pull Requests Title](#-pull-requests-title) +- [๐Ÿชต Commit Log](#-commit-log) + +

+ +## ๐Ÿ’ฌ Commit Message + +I recommend you to follow [Conventional Commits] with this format. + +```commit message +type(scope): Description + +Body +``` + +[Conventional Commits]: https://www.conventionalcommits.org + +
+ +## ๐ŸŽ‹ Pull Requests Branch + +I use the [`dev`] branch for the temporary merging. +When it's time to release, I'll merge the [`dev`] branch into the [`main`] branch. +
+For this reason, please fork the [`dev`] branch and open a PR to it. + +[`main`]: https://github.com/5ouma/rproxy/tree/main +[`dev`]: https://github.com/5ouma/rproxy/tree/dev + +```mermaid +flowchart LR + subgraph PR Branches + feature1 + feature2 + feature3 + end + + subgraph Origin Branches + dev + main + end + + feature1 & feature2 & feature3 -- Squash Merge --> dev + dev -- Rebase Merge --> main +``` + +
+ +## โ“ Pull Requests Title + +You don't need to add any prefixes like `feature` or `bug fix` +to the Pull Requests title. +I can recognize what kind of PR it is from labels. +Please give a clear title. + +
+ +## ๐Ÿชต Commit Log + +I do squash merge to the dev branch to keep the commit history clean. +When merging your Pull Request, I'll add the Conventional Commits type and scope. diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..7fc5945 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,137 @@ +

Reproxy

+ +
+ +**๐Ÿšš Deliver any files in the GitHub repository** + +[![GitHub Release](https://img.shields.io/github/v/release/5ouma/reproxy?style=flat-square)](https://github.com/5ouma/reproxy/releases) +![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/5ouma/reproxy?style=flat-square) +![GitHub repo size](https://img.shields.io/github/repo-size/5ouma/reproxy?style=flat-square) +[![GitHub last commit](https://img.shields.io/github/last-commit/5ouma/reproxy?style=flat-square)](https://github.com/5ouma/reproxy/commit/HEAD) +[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/5ouma/reproxy?style=flat-square)](https://github.com/5ouma/reproxy/commits/main) +
+[![Test](https://img.shields.io/github/actions/workflow/status/5ouma/reproxy/test.yml?label=test&style=flat-square)](https://github.com/5ouma/reproxy/actions/workflows/test.yml) +[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/5ouma/reproxy/main.svg?style=flat-square)](https://results.pre-commit.ci/latest/github/5ouma/reproxy/main) + +
+ +

+ +## ๐Ÿ’ก Concepts + +You can host your specific file on the GitHub repository. +The usage I assume is for one-line scripts. +(i.e. dotfiles or some install scripts, like `curl https://example.com | sh`) + +If you access from browsers, you'll redirected to the GitHub page, not a raw file. + +For the development or testing, you can access to the different branches or tags. +To do this, simply add the ref name to the sub-directory. +(i.e. `curl https://example.com/ref | sh`) + +

+ +## ๐Ÿ”ง Setup + +You can host your file on [Deno Deploy](https://deno.com/deploy). + +### ๐Ÿ’ช Manual Deployment + +1. Clone this repository + + ```sh + git clone https://github.com/5ouma/reproxy.git + ``` + +2. Copy the [`.env.tmpl`](../.env.tmpl) to `.env` and edit as you prefer + + > [๐ŸŒ Environment Variables](#-environment-variables) + +3. Install [Deno](https://deno.com) and [deployctl](https://docs.deno.com/deploy/manual/deployctl). + +4. Deploy to Deno Deploy + + ```sh + deployctl deploy --prod --env-file='.env' + ``` + +
+ +### โš™๏ธ Automatic Deployment + +1. [Fork this repository](https://github.com/5ouma/reproxy/fork) + +2. [Create a new project](https://dash.deno.com/new_project) with your forked repository + +3. Set the environment variables + (_Don't forget!!_) + + > [๐ŸŒ Environment Variables](#-environment-variables) + +

+ +## ๐Ÿ”จ Development + +1. Clone this repository + + ```sh + git clone https://github.com/5ouma/reproxy.git + ``` + +2. Copy the [`.env.template`](../.env.template) to `.env` and edit as you prefer + + > [๐ŸŒ Environment Variables](#-environment-variables) + +3. Install [Deno](https://deno.com) + +4. Run the [`server.ts`](../src/server.ts) via these task runners + + ```sh + # For production + deno task start + ``` + + ```sh + # For development + deno task dev + ``` + +

+ +## ๐ŸŒ Environment Variables + +| Name | Required | +| :----------------: | :------: | +| `REPOSITORY_OWNER` | true | +| `REPOSITORY_NAME` | true | +| `REPOSITORY_PATH` | true | +| [`GITHUB_TOKEN`] | false | + +> [!NOTE] +> You need to add [`GITHUB_TOKEN`] if you want to: +> +> - Access the file in the private repository +> - Avoid the API usage limit + +[`GITHUB_TOKEN`]: https://github.com/settings/tokens/new?scopes=repo + +

+ +## ๐Ÿ†˜ Help + +- [**โš ๏ธ Issues**]: Feature Requests or Bug Reports +- [**๐Ÿ’ฌ Discussions**]: General Chats or Questions +- [**๐Ÿ›ก๏ธ Security Advisories**]: Security Issues that should not be public + +[**โš ๏ธ Issues**]: https://github.com/5ouma/reproxy/issues/new/choose +[**๐Ÿ’ฌ Discussions**]: https://github.com/5ouma/reproxy/discussions/new/choose +[**๐Ÿ›ก๏ธ Security Advisories**]: https://github.com/5ouma/reproxy/security/advisories/new + +

+ +## ๐ŸŽฝ Contributing + +I happily welcome your contributions! +Before you contribute, +I would recommend reading [CONTRIBUTING.md](./CONTRIBUTING.md) +for a better development experience. diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..392610a --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,8 @@ +# Security Policy + +## ๐Ÿ›ก๏ธ Reporting a Vulnerability + +If you find any issues of security, please report on [Security Advisories](https://github.com/5ouma/reproxy/security/advisories/new). + +> [!CAUTION] +> Please do NOT open Issues. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..009c738 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Souma + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index e69de29..0000000 From 83042b1b3d1a6900e4ab75dac54ab2cc06b824fe Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:44:36 +0900 Subject: [PATCH 11/19] docs(contributing): Add templates for Issue or PR It makes contributing easier. --- .github/ISSUE_TEMPLATE/1-feature-request.yml | 33 ++++++++++++++++ .github/ISSUE_TEMPLATE/2-bug-report.yml | 38 ++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 9 +++++ .github/PULL_REQUEST_TEMPLATE.md | 41 ++++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/1-feature-request.yml create mode 100644 .github/ISSUE_TEMPLATE/2-bug-report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE/1-feature-request.yml b/.github/ISSUE_TEMPLATE/1-feature-request.yml new file mode 100644 index 0000000..4c93fd0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-feature-request.yml @@ -0,0 +1,33 @@ +name: ๐ŸŽ‰ Feature Request +description: Request a new feature or enhancement to an existing feature +labels: ["Type: Feature"] +body: + - type: textarea + id: feature-description + attributes: + label: โœ๏ธ Describe the feature + description: A clear and concise description + placeholder: | + - I want a feature to... + - This behavior should be... + validations: + required: true + + - type: checkboxes + id: checks + attributes: + label: โœ”๏ธŽ Confirm these checks + description: Make sure you reviewed below + options: + - label: I searched the GitHub [Issues](https://github.com/5ouma/reproxy/issues) or [Discussions](https://github.com/5ouma/reproxy/discussions). + required: true + - label: I tried with the latest version. + required: false + - label: I agree to follow the [Code of Conduct](https://github.com/5ouma/reproxy/blob/main/.github/CODE_OF_CONDUCT.md). + required: true + + - type: textarea + id: additional-info + attributes: + label: ๐Ÿ—’๏ธ Additional Information + description: Anything else you want to add diff --git a/.github/ISSUE_TEMPLATE/2-bug-report.yml b/.github/ISSUE_TEMPLATE/2-bug-report.yml new file mode 100644 index 0000000..b7d868b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-bug-report.yml @@ -0,0 +1,38 @@ +name: ๐Ÿงฐ Bug Report +description: Something doesn't work +labels: ["Type: Bug"] +body: + - type: textarea + id: bug-description + attributes: + label: โœ๏ธ Describe the Bug + description: A clear and concise description + validations: + required: true + + - type: textarea + id: reproduction-steps + attributes: + label: ๐Ÿญ How to Reproduce the Bug + description: Steps to reproduce the behavior + validations: + required: true + + - type: checkboxes + id: checks + attributes: + label: โœ”๏ธŽ Confirm these checks + description: Make sure you reviewed below + options: + - label: I searched the GitHub [Issues](https://github.com/5ouma/reproxy/issues) or [Discussions](https://github.com/5ouma/reproxy/discussions). + required: true + - label: I tried with the latest version. + required: false + - label: I agree to follow the [Code of Conduct](https://github.com/5ouma/reproxy/blob/main/.github/CODE_OF_CONDUCT.md). + required: true + + - type: textarea + id: additional-info + attributes: + label: ๐Ÿ—’๏ธ Additional Information + description: Anything else you want to add diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..4cfc137 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,9 @@ +blank_issues_enabled: false +contact_links: + - name: ๐Ÿ’ฌ General Discussions + url: https://github.com/5ouma/reproxy/discussions/new?category=general + about: Chat about anything and everything here + + - name: ๐Ÿ™ Q&A + url: https://github.com/5ouma/reproxy/discussions/new?category=q-a + about: Ask the community for help diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..f2c8b61 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,41 @@ + + +### โš ๏ธ Issue + +close # + +
+ +### โœ”๏ธŽ Checklists + +- [ ] This Pull Request introduces a new feature. +- [ ] This Pull Request fixes a bug. + +
+ +### ๐Ÿ”„ Type of the Change + +- [ ] ๐ŸŽ‰ Feature +- [ ] ๐Ÿงฐ Bug +- [ ] ๐Ÿ›ก๏ธ Security +- [ ] ๐Ÿ“– Documentation +- [ ] ๐Ÿงน Refactoring +- [ ] ๐Ÿงช Testing +- [ ] ๐Ÿ”ง Maintenance +- [ ] ๐ŸŽฝ CI +- [ ] โ›“๏ธ Dependencies +- [ ] ๐Ÿง  Meta + +
+ +### โœ๏ธ Description + + + +
+ +- [ ] I agree to follow the [Code of Conduct](https://github.com/5ouma/reproxy/blob/main/.github/CODE_OF_CONDUCT.md). From 7c44258183422980c72e9ed32d7ec072a08f3d6e Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:45:54 +0900 Subject: [PATCH 12/19] ci(actions): Add workflow files Test and deps update. --- .github/workflows/deps-update.yml | 29 ++++++++++++++++++++++++++++ .github/workflows/test.yml | 32 +++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 .github/workflows/deps-update.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/deps-update.yml b/.github/workflows/deps-update.yml new file mode 100644 index 0000000..b9f427c --- /dev/null +++ b/.github/workflows/deps-update.yml @@ -0,0 +1,29 @@ +name: ๐Ÿค– Dependencies Update + +on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + Update: + runs-on: Ubuntu-Latest + + steps: + - name: ๐Ÿšš Checkout Repository + uses: actions/checkout@v4 + with: + ref: dev + + - name: ๐Ÿฆ• Update Dependencies + uses: hasundue/molt-action@v1-rc + with: + author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> + base: dev + branch: deps-deno + commit-prefix: "chore(deps):" + labels: "Type: Dependencies" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..19d081e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,32 @@ +name: ๐Ÿงช Test + +on: + push: + branches: + - main + - dev + paths: + - "**.ts" + - "deno.lock" + - ".github/workflows/test.yml" + pull_request: + paths: + - "**.ts" + - "deno.lock" + - ".github/workflows/test.yml" + +jobs: + Test: + runs-on: Ubuntu-Latest + + steps: + - name: ๐Ÿšš Checkout Repository + uses: actions/checkout@v4 + + - name: ๐Ÿฆ• Setup Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + + - name: ๐Ÿงช Test Libraries + run: deno task test From 82f2dc609c0d9d379ab6d5f4f35871f496704bee Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:47:15 +0900 Subject: [PATCH 13/19] ci(apps): GitHub Apps configs They support comfortable development. --- .github/auto_assign.yml | 3 ++ .github/branch-switcher.yml | 2 + .github/delete-merged-branch-config.yml | 2 + .github/dependabot.yml | 6 +++ .github/pr-labeler.yml | 27 +++++++++++++ .github/release.yml | 30 +++++++++++++++ .pre-commit-config.yaml | 50 +++++++++++++++++++++++++ 7 files changed, 120 insertions(+) create mode 100644 .github/auto_assign.yml create mode 100644 .github/branch-switcher.yml create mode 100644 .github/delete-merged-branch-config.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/pr-labeler.yml create mode 100644 .github/release.yml create mode 100644 .pre-commit-config.yaml diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml new file mode 100644 index 0000000..fbaffa6 --- /dev/null +++ b/.github/auto_assign.yml @@ -0,0 +1,3 @@ +addReviewers: true +addAssignees: author +reviewers: ["5ouma"] diff --git a/.github/branch-switcher.yml b/.github/branch-switcher.yml new file mode 100644 index 0000000..ca38c7a --- /dev/null +++ b/.github/branch-switcher.yml @@ -0,0 +1,2 @@ +preferredBranch: "dev" +exclude: [branch: "dev"] diff --git a/.github/delete-merged-branch-config.yml b/.github/delete-merged-branch-config.yml new file mode 100644 index 0000000..420d74f --- /dev/null +++ b/.github/delete-merged-branch-config.yml @@ -0,0 +1,2 @@ +exclude: ["dev"] +delete_closed_pr: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1230149 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/pr-labeler.yml b/.github/pr-labeler.yml new file mode 100644 index 0000000..afbf925 --- /dev/null +++ b/.github/pr-labeler.yml @@ -0,0 +1,27 @@ +alwaysReplace: true +searchTitle: true +searchBody: true + +customLabels: + - text: "- [x] ๐ŸŽ‰ Feature" + label: "Type: Feature" + - text: "- [x] ๐Ÿงฐ Bug" + label: "Type: Bug" + - text: "- [x] ๐Ÿ›ก๏ธ Security" + label: "Type: Security" + - text: "- [x] ๐Ÿ“– Documentation" + label: "Type: Documentation" + - text: "- [x] ๐Ÿงน Refactoring" + label: "Type: Refactoring" + - text: "- [x] ๐Ÿงช Testing" + label: "Type: Testing" + - text: "- [x] ๐Ÿ”ง Maintenance" + label: "Type: Maintenance" + - text: "- [x] ๐ŸŽฝ CI" + label: "Type: CI" + - text: "- [x] โ›“๏ธ Dependencies" + label: "Type: Dependencies" + - text: "chore(deps): " + label: "Type: Dependencies" + - text: "- [x] ๐Ÿง  Meta" + label: "Type: Meta" diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..fc7e406 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,30 @@ +changelog: + exclude: + labels: + - "Type: Meta" + - "Type: Question" + - "Type: Release" + + categories: + - title: ๐Ÿงจ Breaking Changes + labels: ["Type: Breaking Change"] + - title: ๐ŸŽ‰ New Features + labels: ["Type: Feature"] + - title: ๐Ÿงฐ Bug Fixes + labels: ["Type: Bug"] + - title: ๐Ÿ›ก๏ธ Security Fixes + labels: ["Type: Security"] + - title: ๐Ÿ“– Documentation Changes + labels: ["Type: Documentation"] + - title: ๐Ÿงน Refactoring + labels: ["Type: Refactoring"] + - title: ๐Ÿงช Testing + labels: ["Type: Testing"] + - title: ๐Ÿ”ง Maintenance + labels: ["Type: Maintenance"] + - title: ๐ŸŽฝ CI + labels: ["Type: CI"] + - title: โ›“๏ธ Dependency Updates + labels: ["Type: Dependencies", "dependencies"] + - title: ๐Ÿ” Other Changes + labels: ["*"] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..75dd150 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,50 @@ +ci: + autofix_commit_msg: | + chore(fix): Auto fixes from pre-commit.com hooks + + For more information, see https://pre-commit.ci. + autoupdate_branch: "dev" + autoupdate_commit_msg: "chore(deps): Bump pre-commit hook" + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-json + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + + - repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.5.5 + hooks: + - id: remove-tabs + args: [--whitespaces-count, "2"] + + - repo: https://github.com/crate-ci/typos + rev: v1.22.0 + hooks: + - id: typos + + - repo: https://github.com/gitleaks/gitleaks + rev: v8.18.3 + hooks: + - id: gitleaks + + - repo: https://github.com/rhysd/actionlint + rev: v1.7.1 + hooks: + - id: actionlint + + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.41.0 + hooks: + - id: markdownlint-fix + args: ["--disable", "MD013", "MD023", "MD024", "MD033", "MD036"] + + - repo: https://github.com/koalaman/shellcheck-precommit + rev: v0.10.0 + hooks: + - id: shellcheck + args: ["-e", "SC1071,SC1072,SC1073,SC1090,SC1091,SC2015,SC2148,SC2154"] From 3644678fba6123b5770cd0f3003ae745fc8fc298 Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:48:01 +0900 Subject: [PATCH 14/19] build(deploy): Add config for Deno Deploy Users can deploy this project with less settings. --- deno.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deno.json b/deno.json index 7e53786..5f1620c 100644 --- a/deno.json +++ b/deno.json @@ -16,5 +16,10 @@ "@std/http": "jsr:@std/http@0.224.3", "@std/http/user-agent": "jsr:@std/http@~0.223/user-agent", "@std/path": "jsr:@std/path@0.225.2" + }, + "deploy": { + "project": "reproxy", + "include": ["./deno.json", "./src/"], + "entrypoint": "./src/server.ts" } } From dc69f2d0b3762a2ac4b8689175ef69e8bbdba59d Mon Sep 17 00:00:00 2001 From: 5ouma <101255979+5ouma@users.noreply.github.com> Date: Fri, 7 Jun 2024 19:06:09 +0900 Subject: [PATCH 15/19] style(test): Write more clean test code Tests are meaningful and readable. --- test/libs/content_test.ts | 6 +++--- test/libs/env_test.ts | 18 ++++++++---------- test/libs/redirect_test.ts | 14 +++++++------- test/utils.ts | 6 ++++++ 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/test/libs/content_test.ts b/test/libs/content_test.ts index 05e091b..b87c18e 100644 --- a/test/libs/content_test.ts +++ b/test/libs/content_test.ts @@ -7,24 +7,24 @@ import { testRepo, unknownRepo, testRef, exportRepo } from "../utils.ts"; Deno.test("Get Content", async () => { const ctx: RouterContext = testing.createMockContext(); exportRepo(testRepo); - await getContent(ctx); + assertEquals(ctx.response.status, STATUS_CODE.OK); }); Deno.test("Get Content (With ref)", async () => { const ctx: RouterContext = testing.createMockContext(); exportRepo(testRepo); - await getContent(ctx, testRef); + assertEquals(ctx.response.status, STATUS_CODE.OK); }); Deno.test("Get Content (Not found)", async () => { const ctx: RouterContext = testing.createMockContext(); exportRepo(unknownRepo); - await getContent(ctx); + assertEquals(ctx.response.status, STATUS_CODE.NotFound); assertStringIncludes( ctx.response.body!.toString(), diff --git a/test/libs/env_test.ts b/test/libs/env_test.ts index 996b7cd..48dff26 100644 --- a/test/libs/env_test.ts +++ b/test/libs/env_test.ts @@ -1,22 +1,20 @@ import { assertEquals } from "@std/assert"; import { getRepository } from "../../src/libs/env.ts"; -import { unknownRepo, exportRepo } from "../utils.ts"; +import { testRepo, exportRepo, clearRepo } from "../utils.ts"; Deno.test("Get Repository Env", () => { - exportRepo(unknownRepo); - + exportRepo(testRepo); const repository = getRepository(); - assertEquals(repository.owner, unknownRepo.owner); - assertEquals(repository.name, unknownRepo.name); - assertEquals(repository.path, unknownRepo.path); + + assertEquals(repository.owner, testRepo.owner); + assertEquals(repository.name, testRepo.name); + assertEquals(repository.path, testRepo.path); }); Deno.test("Get Repository Env (Not set)", () => { - Deno.env.delete("REPOSITORY_OWNER"); - Deno.env.delete("REPOSITORY_NAME"); - Deno.env.delete("REPOSITORY_PATH"); - + clearRepo(); const repository = getRepository(); + assertEquals(repository.owner, ""); assertEquals(repository.name, ""); assertEquals(repository.path, ""); diff --git a/test/libs/redirect_test.ts b/test/libs/redirect_test.ts index 588a565..979abac 100644 --- a/test/libs/redirect_test.ts +++ b/test/libs/redirect_test.ts @@ -3,28 +3,28 @@ import { assertEquals } from "@std/assert"; import { STATUS_CODE } from "@std/http/status"; import { UserAgent } from "@std/http/user-agent"; import { redirect } from "../../src/libs/redirect.ts"; -import { unknownRepo, testRef, exportRepo } from "../utils.ts"; +import { testRepo, testRef, exportRepo } from "../utils.ts"; Deno.test("Redirect Detection", () => { const ctx: RouterContext = testing.createMockContext(); - exportRepo(unknownRepo); - + exportRepo(testRepo); redirect(ctx, new UserAgent("Chrome/1.2.3")); + assertEquals(ctx.response.status, STATUS_CODE.PermanentRedirect); assertEquals( ctx.response.headers.get("location"), - `https://github.com/${unknownRepo.owner}/${unknownRepo.name}/blob/HEAD/${unknownRepo.path}` + `https://github.com/${testRepo.owner}/${testRepo.name}/blob/HEAD/${testRepo.path}` ); }); Deno.test("Redirect Detection (With ref)", () => { const ctx: RouterContext = testing.createMockContext(); - exportRepo(unknownRepo); - + exportRepo(testRepo); redirect(ctx, new UserAgent("Chrome/1.2.3"), testRef); + assertEquals(ctx.response.status, STATUS_CODE.PermanentRedirect); assertEquals( ctx.response.headers.get("location"), - `https://github.com/${unknownRepo.owner}/${unknownRepo.name}/blob/${testRef}/${unknownRepo.path}` + `https://github.com/${testRepo.owner}/${testRepo.name}/blob/${testRef}/${testRepo.path}` ); }); diff --git a/test/utils.ts b/test/utils.ts index b576b13..bccbd56 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -19,3 +19,9 @@ export function exportRepo(repository: Repository) { Deno.env.set("REPOSITORY_NAME", repository.name); Deno.env.set("REPOSITORY_PATH", repository.path); } + +export function clearRepo() { + Deno.env.delete("REPOSITORY_OWNER"); + Deno.env.delete("REPOSITORY_NAME"); + Deno.env.delete("REPOSITORY_PATH"); +} From c2a7fb95d84c72660cadd18d33d7b554b0e15cec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 12 Jun 2024 16:55:44 +0900 Subject: [PATCH 16/19] chore(deps): update dependencies (#2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - @oak/oak [16.0.0](https://jsr.io/@oak/oak/16.0.0) โ†’ [16.1.0](https://jsr.io/@oak/oak/16.1.0) - @std/fmt [0.225.3](https://jsr.io/@std/fmt/0.225.3) โ†’ [0.225.4](https://jsr.io/@std/fmt/0.225.4) - @std/http [0.224.3](https://jsr.io/@std/http/0.224.3) โ†’ [0.224.4](https://jsr.io/@std/http/0.224.4) --- deno.json | 6 ++-- deno.lock | 95 +++++++++++++++++++++++++++---------------------------- 2 files changed, 49 insertions(+), 52 deletions(-) diff --git a/deno.json b/deno.json index 5f1620c..a4c58f9 100644 --- a/deno.json +++ b/deno.json @@ -9,11 +9,11 @@ "test": "deno test --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH,GITHUB_TOKEN' --allow-net='api.github.com' --parallel --shuffle" }, "imports": { - "@oak/oak": "jsr:@oak/oak@16.0.0", + "@oak/oak": "jsr:@oak/oak@16.1.0", "@octokit/rest": "https://esm.sh/@octokit/rest@20.1.1", "@std/assert": "jsr:@std/assert@0.226.0", - "@std/fmt": "jsr:@std/fmt@0.225.3", - "@std/http": "jsr:@std/http@0.224.3", + "@std/fmt": "jsr:@std/fmt@0.225.4", + "@std/http": "jsr:@std/http@0.224.4", "@std/http/user-agent": "jsr:@std/http@~0.223/user-agent", "@std/path": "jsr:@std/path@0.225.2" }, diff --git a/deno.lock b/deno.lock index 52911b8..39b61a8 100644 --- a/deno.lock +++ b/deno.lock @@ -2,49 +2,49 @@ "version": "3", "packages": { "specifiers": { - "jsr:@oak/commons@0.10": "jsr:@oak/commons@0.10.1", - "jsr:@oak/oak@16.0.0": "jsr:@oak/oak@16.0.0", - "jsr:@std/assert@0.222": "jsr:@std/assert@0.222.1", + "jsr:@oak/commons@0.11": "jsr:@oak/commons@0.11.0", + "jsr:@oak/oak@16.1.0": "jsr:@oak/oak@16.1.0", "jsr:@std/assert@0.223": "jsr:@std/assert@0.223.0", + "jsr:@std/assert@0.226": "jsr:@std/assert@0.226.0", "jsr:@std/assert@0.226.0": "jsr:@std/assert@0.226.0", - "jsr:@std/assert@^0.222.1": "jsr:@std/assert@0.222.1", "jsr:@std/assert@^0.223.0": "jsr:@std/assert@0.223.0", + "jsr:@std/assert@^0.224.0": "jsr:@std/assert@0.224.0", "jsr:@std/assert@^0.226.0": "jsr:@std/assert@0.226.0", - "jsr:@std/bytes@0.222": "jsr:@std/bytes@0.222.1", "jsr:@std/bytes@0.223": "jsr:@std/bytes@0.223.0", + "jsr:@std/bytes@0.224": "jsr:@std/bytes@0.224.0", "jsr:@std/bytes@^0.223.0": "jsr:@std/bytes@0.223.0", - "jsr:@std/crypto@0.222": "jsr:@std/crypto@0.222.1", "jsr:@std/crypto@0.223": "jsr:@std/crypto@0.223.0", - "jsr:@std/encoding@^0.222.1": "jsr:@std/encoding@0.222.1", + "jsr:@std/crypto@0.224": "jsr:@std/crypto@0.224.0", + "jsr:@std/encoding@1.0.0-rc.2": "jsr:@std/encoding@1.0.0-rc.2", "jsr:@std/encoding@^0.223.0": "jsr:@std/encoding@0.223.0", - "jsr:@std/fmt@0.225.3": "jsr:@std/fmt@0.225.3", - "jsr:@std/http@0.222": "jsr:@std/http@0.222.1", + "jsr:@std/fmt@0.225.4": "jsr:@std/fmt@0.225.4", "jsr:@std/http@0.223": "jsr:@std/http@0.223.0", - "jsr:@std/http@0.224.3": "jsr:@std/http@0.224.3", + "jsr:@std/http@0.224": "jsr:@std/http@0.224.4", + "jsr:@std/http@0.224.4": "jsr:@std/http@0.224.4", "jsr:@std/http@~0.223": "jsr:@std/http@0.223.0", "jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.0", "jsr:@std/io@0.223": "jsr:@std/io@0.223.0", - "jsr:@std/media-types@0.222": "jsr:@std/media-types@0.222.1", "jsr:@std/media-types@0.223": "jsr:@std/media-types@0.223.0", + "jsr:@std/media-types@0.224": "jsr:@std/media-types@0.224.1", "jsr:@std/path@0.223": "jsr:@std/path@0.223.0", "jsr:@std/path@0.225.2": "jsr:@std/path@0.225.2", "npm:path-to-regexp@6.2.1": "npm:path-to-regexp@6.2.1" }, "jsr": { - "@oak/commons@0.10.1": { - "integrity": "4775ebf70782b0c5d95958d85b2425b3fbdff06f57654aa4143dbcb23b923cf4", + "@oak/commons@0.11.0": { + "integrity": "07702bfe5c07cd8144c422022994da1f9fea466b185824f4be63a2b1b1a65125", "dependencies": [ - "jsr:@std/assert@0.222", - "jsr:@std/bytes@0.222", - "jsr:@std/crypto@0.222", - "jsr:@std/http@0.222", - "jsr:@std/media-types@0.222" + "jsr:@std/assert@0.226", + "jsr:@std/bytes@0.224", + "jsr:@std/crypto@0.224", + "jsr:@std/http@0.224", + "jsr:@std/media-types@0.224" ] }, - "@oak/oak@16.0.0": { - "integrity": "a98756fb4bf69f728c9a5179ff4157620946c5969bcaa3d6bd64cca8b198f74e", + "@oak/oak@16.1.0": { + "integrity": "ab21506555fffeb08dc8f45ff5d28607e8087949ff58bd2964b27df65994480b", "dependencies": [ - "jsr:@oak/commons@0.10", + "jsr:@oak/commons@0.11", "jsr:@std/assert@0.223", "jsr:@std/bytes@0.223", "jsr:@std/crypto@0.223", @@ -55,29 +55,23 @@ "npm:path-to-regexp@6.2.1" ] }, - "@std/assert@0.222.1": { - "integrity": "691637161ee584a9919d1f9950ddd1272feb8e0a19e83aa5b7563cedaf73d74c" - }, "@std/assert@0.223.0": { "integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24" }, + "@std/assert@0.224.0": { + "integrity": "8643233ec7aec38a940a8264a6e3eed9bfa44e7a71cc6b3c8874213ff401967f" + }, "@std/assert@0.226.0": { "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3", "dependencies": [ "jsr:@std/internal@^1.0.0" ] }, - "@std/bytes@0.222.1": { - "integrity": "04dbf33e889ed5f9f6a87bc6b6b882dc105fffae6d2b042f832d92d4f34771c0" - }, "@std/bytes@0.223.0": { "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8" }, - "@std/crypto@0.222.1": { - "integrity": "d5b9e6c704fadbcc384cd42c0b603ad4aea710ece0ff26426602681b64fd237c", - "dependencies": [ - "jsr:@std/assert@^0.222.1" - ] + "@std/bytes@0.224.0": { + "integrity": "a2250e1d0eb7d1c5a426f21267ab9bdeac2447fa87a3d0d1a467d3f7a6058e49" }, "@std/crypto@0.223.0": { "integrity": "1aa9555ff56b09e197ad988ea200f84bc6781fd4fd83f3a156ee44449af93000", @@ -85,20 +79,20 @@ "jsr:@std/encoding@^0.223.0" ] }, - "@std/encoding@0.222.1": { - "integrity": "fb6c1d38722feebc8d4a5efa3eb2039ecec0d50d053186240484d0c4a4ce1006" + "@std/crypto@0.224.0": { + "integrity": "154ef3ff08ef535562ef1a718718c5b2c5fc3808f0f9100daad69e829bfcdf2d", + "dependencies": [ + "jsr:@std/assert@^0.224.0" + ] }, "@std/encoding@0.223.0": { "integrity": "2b5615a75e00337ce113f34cf2f9b8c18182c751a8dcc8b1a2c2fc0e117bef00" }, - "@std/fmt@0.225.3": { - "integrity": "cb6ea567155f9865b80b502b2dde7671803eddd6dad743d8851d0de2c40bd349" + "@std/encoding@1.0.0-rc.2": { + "integrity": "160d7674a20ebfbccdf610b3801fee91cf6e42d1c106dd46bbaf46e395cd35ef" }, - "@std/http@0.222.1": { - "integrity": "a3c731ec6040927aa37d8378b9e6b467e2e3068c1f97838a6e79ee3bdc103521", - "dependencies": [ - "jsr:@std/encoding@^0.222.1" - ] + "@std/fmt@0.225.4": { + "integrity": "584c681cf422b70e28959b57e59012823609c087384cbf12d05f67814797fda3" }, "@std/http@0.223.0": { "integrity": "15ab8a0c5a7e9d5be017a15b01600f20f66602ceec48b378939fa24fcec522aa", @@ -107,8 +101,11 @@ "jsr:@std/encoding@^0.223.0" ] }, - "@std/http@0.224.3": { - "integrity": "dac3a3640c730b9a4bcdb916b5231f4f2f5c141daf8dafc2b46011e1dab08eed" + "@std/http@0.224.4": { + "integrity": "7bcae028e408ab65d6802805ee9a807766c9d20a2959de576126e2f25c17ec04", + "dependencies": [ + "jsr:@std/encoding@1.0.0-rc.2" + ] }, "@std/internal@1.0.0": { "integrity": "ac6a6dfebf838582c4b4f61a6907374e27e05bedb6ce276e0f1608fe84e7cd9a" @@ -119,12 +116,12 @@ "jsr:@std/bytes@^0.223.0" ] }, - "@std/media-types@0.222.1": { - "integrity": "147cbd7f29fb4480625ccdad679637f945437f455534049eb7c95977b973a137" - }, "@std/media-types@0.223.0": { "integrity": "84684680c2eb6bc6d9369c6d6f26a49decaf2c7603ff531862dda575d9d6776e" }, + "@std/media-types@0.224.1": { + "integrity": "9e69a5daed37c5b5c6d3ce4731dc191f80e67f79bed392b0957d1d03b87f11e1" + }, "@std/path@0.223.0": { "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989" }, @@ -162,10 +159,10 @@ }, "workspace": { "dependencies": [ - "jsr:@oak/oak@16.0.0", + "jsr:@oak/oak@16.1.0", "jsr:@std/assert@0.226.0", - "jsr:@std/fmt@0.225.3", - "jsr:@std/http@0.224.3", + "jsr:@std/fmt@0.225.4", + "jsr:@std/http@0.224.4", "jsr:@std/http@~0.223", "jsr:@std/path@0.225.2" ] From e1ea1aa37c2c5449a3bd77599dc29eb9febc1948 Mon Sep 17 00:00:00 2001 From: Souma <101255979+5ouma@users.noreply.github.com> Date: Wed, 12 Jun 2024 20:15:25 +0900 Subject: [PATCH 17/19] refactor(redirect): Use @std/url and change to "master" (#4) - Because of the processing URL, not the directory path. - Redirects to the default branch, not head hash. --- deno.json | 2 +- deno.lock | 20 ++++++++++++++------ src/libs/redirect.ts | 22 ++++++++++------------ test/libs/redirect_test.ts | 2 +- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/deno.json b/deno.json index a4c58f9..8227c40 100644 --- a/deno.json +++ b/deno.json @@ -15,7 +15,7 @@ "@std/fmt": "jsr:@std/fmt@0.225.4", "@std/http": "jsr:@std/http@0.224.4", "@std/http/user-agent": "jsr:@std/http@~0.223/user-agent", - "@std/path": "jsr:@std/path@0.225.2" + "@std/url": "jsr:@std/url@0.224.1" }, "deploy": { "project": "reproxy", diff --git a/deno.lock b/deno.lock index 39b61a8..8f0b788 100644 --- a/deno.lock +++ b/deno.lock @@ -9,7 +9,6 @@ "jsr:@std/assert@0.226.0": "jsr:@std/assert@0.226.0", "jsr:@std/assert@^0.223.0": "jsr:@std/assert@0.223.0", "jsr:@std/assert@^0.224.0": "jsr:@std/assert@0.224.0", - "jsr:@std/assert@^0.226.0": "jsr:@std/assert@0.226.0", "jsr:@std/bytes@0.223": "jsr:@std/bytes@0.223.0", "jsr:@std/bytes@0.224": "jsr:@std/bytes@0.224.0", "jsr:@std/bytes@^0.223.0": "jsr:@std/bytes@0.223.0", @@ -27,7 +26,9 @@ "jsr:@std/media-types@0.223": "jsr:@std/media-types@0.223.0", "jsr:@std/media-types@0.224": "jsr:@std/media-types@0.224.1", "jsr:@std/path@0.223": "jsr:@std/path@0.223.0", - "jsr:@std/path@0.225.2": "jsr:@std/path@0.225.2", + "jsr:@std/path@1.0.0-rc.1": "jsr:@std/path@1.0.0-rc.1", + "jsr:@std/url@0.224.1": "jsr:@std/url@0.224.1", + "npm:@types/node": "npm:@types/node@18.16.19", "npm:path-to-regexp@6.2.1": "npm:path-to-regexp@6.2.1" }, "jsr": { @@ -125,14 +126,21 @@ "@std/path@0.223.0": { "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989" }, - "@std/path@0.225.2": { - "integrity": "0f2db41d36b50ef048dcb0399aac720a5348638dd3cb5bf80685bf2a745aa506", + "@std/path@1.0.0-rc.1": { + "integrity": "b8c00ae2f19106a6bb7cbf1ab9be52aa70de1605daeb2dbdc4f87a7cbaf10ff6" + }, + "@std/url@0.224.1": { + "integrity": "19f0e1c6ff8fc2ef9522a746595c24f53be47425daa910eb8ef39cbb5e00b3df", "dependencies": [ - "jsr:@std/assert@^0.226.0" + "jsr:@std/path@1.0.0-rc.1" ] } }, "npm": { + "@types/node@18.16.19": { + "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", + "dependencies": {} + }, "path-to-regexp@6.2.1": { "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", "dependencies": {} @@ -164,7 +172,7 @@ "jsr:@std/fmt@0.225.4", "jsr:@std/http@0.224.4", "jsr:@std/http@~0.223", - "jsr:@std/path@0.225.2" + "jsr:@std/url@0.224.1" ] } } diff --git a/src/libs/redirect.ts b/src/libs/redirect.ts index 951b374..6434055 100644 --- a/src/libs/redirect.ts +++ b/src/libs/redirect.ts @@ -1,25 +1,23 @@ import type { RouterContext } from "@oak/oak"; import { STATUS_CODE } from "@std/http/status"; import { UserAgent } from "@std/http/user-agent"; -import { join } from "@std/path"; +import { join } from "@std/url"; import { getRepository } from "./env.ts"; export function redirect( ctx: RouterContext, userAgent: UserAgent, - ref: string = "HEAD" + ref: string = "master" ): void { const repository = getRepository(); - const url: string = - "https://" + - join( - "github.com", - repository.owner, - repository.name, - "blob", - ref, - repository.path - ); + const url = join( + new URL("https://github.com"), + repository.owner, + repository.name, + "blob", + ref, + repository.path + ); if (userAgent?.browser.name) { ctx.response.status = STATUS_CODE.PermanentRedirect; diff --git a/test/libs/redirect_test.ts b/test/libs/redirect_test.ts index 979abac..06d9c1e 100644 --- a/test/libs/redirect_test.ts +++ b/test/libs/redirect_test.ts @@ -13,7 +13,7 @@ Deno.test("Redirect Detection", () => { assertEquals(ctx.response.status, STATUS_CODE.PermanentRedirect); assertEquals( ctx.response.headers.get("location"), - `https://github.com/${testRepo.owner}/${testRepo.name}/blob/HEAD/${testRepo.path}` + `https://github.com/${testRepo.owner}/${testRepo.name}/blob/master/${testRepo.path}` ); }); From cd4fa2964984f257f4c803efa70a40fce55564a8 Mon Sep 17 00:00:00 2001 From: Souma <101255979+5ouma@users.noreply.github.com> Date: Wed, 12 Jun 2024 20:18:30 +0900 Subject: [PATCH 18/19] ci(test): Run other tests (#5) To clean code and workable. --- .github/workflows/test.yml | 11 ++++++++++- src/libs/content.ts | 2 +- src/libs/env.ts | 6 ++++-- src/libs/redirect.ts | 4 ++-- src/server.ts | 10 ++++++---- test/libs/content_test.ts | 6 +++--- test/libs/env_test.ts | 2 +- test/libs/redirect_test.ts | 8 ++++---- test/router_test.ts | 4 ++-- 9 files changed, 33 insertions(+), 20 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 19d081e..1209870 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,5 +28,14 @@ jobs: with: deno-version: v1.x - - name: ๐Ÿงช Test Libraries + - name: ๐Ÿงช Run Tests run: deno task test + + - name: ๐Ÿงน Lint Check + run: deno lint + + - name: ๐Ÿ“ Format Check + run: deno fmt --check + + - name: ๐Ÿ” Type Check + run: deno check ./**/*.ts diff --git a/src/libs/content.ts b/src/libs/content.ts index e9e8022..0655daa 100644 --- a/src/libs/content.ts +++ b/src/libs/content.ts @@ -4,7 +4,7 @@ import { getRepository, githubToken } from "./env.ts"; export async function getContent( ctx: RouterContext, - ref: string | undefined = undefined + ref: string | undefined = undefined, ): Promise { const octokit = new Octokit({ auth: githubToken }); const repository = getRepository(); diff --git a/src/libs/env.ts b/src/libs/env.ts index 2bc7b03..bbefa71 100644 --- a/src/libs/env.ts +++ b/src/libs/env.ts @@ -7,9 +7,11 @@ export function getRepository(): Repository { path: Deno.env.get("REPOSITORY_PATH") ?? "", }; - for (const key in repository) - if (!repository[key as keyof Repository]) + for (const key in repository) { + if (!repository[key as keyof Repository]) { console.error(`๐Ÿšจ missing: $REPOSITORY_${key.toUpperCase()}`); + } + } return repository; } diff --git a/src/libs/redirect.ts b/src/libs/redirect.ts index 6434055..586de08 100644 --- a/src/libs/redirect.ts +++ b/src/libs/redirect.ts @@ -7,7 +7,7 @@ import { getRepository } from "./env.ts"; export function redirect( ctx: RouterContext, userAgent: UserAgent, - ref: string = "master" + ref: string = "master", ): void { const repository = getRepository(); const url = join( @@ -16,7 +16,7 @@ export function redirect( repository.name, "blob", ref, - repository.path + repository.path, ); if (userAgent?.browser.name) { diff --git a/src/server.ts b/src/server.ts index 3760e80..925c130 100644 --- a/src/server.ts +++ b/src/server.ts @@ -12,11 +12,13 @@ app.addEventListener( "listen", ({ secure, hostname, port }: ApplicationListenEvent) => { console.log( - `๐Ÿ”” listening: ${yellow( - `${secure ? "https" : "http"}://${hostname ?? "localhost"}:${port}` - )}` + `๐Ÿ”” listening: ${ + yellow( + `${secure ? "https" : "http"}://${hostname ?? "localhost"}:${port}`, + ) + }`, ); - } + }, ); await app.listen({ port: 8080 }); diff --git a/test/libs/content_test.ts b/test/libs/content_test.ts index b87c18e..c7bdaba 100644 --- a/test/libs/content_test.ts +++ b/test/libs/content_test.ts @@ -1,8 +1,8 @@ -import { testing, type RouterContext } from "@oak/oak"; +import { type RouterContext, testing } from "@oak/oak"; import { assertEquals, assertStringIncludes } from "@std/assert"; import { STATUS_CODE } from "@std/http/status"; import { getContent } from "../../src/libs/content.ts"; -import { testRepo, unknownRepo, testRef, exportRepo } from "../utils.ts"; +import { exportRepo, testRef, testRepo, unknownRepo } from "../utils.ts"; Deno.test("Get Content", async () => { const ctx: RouterContext = testing.createMockContext(); @@ -28,6 +28,6 @@ Deno.test("Get Content (Not found)", async () => { assertEquals(ctx.response.status, STATUS_CODE.NotFound); assertStringIncludes( ctx.response.body!.toString(), - `โš ๏ธ ${STATUS_CODE.NotFound}:` + `โš ๏ธ ${STATUS_CODE.NotFound}:`, ); }); diff --git a/test/libs/env_test.ts b/test/libs/env_test.ts index 48dff26..448ead6 100644 --- a/test/libs/env_test.ts +++ b/test/libs/env_test.ts @@ -1,6 +1,6 @@ import { assertEquals } from "@std/assert"; import { getRepository } from "../../src/libs/env.ts"; -import { testRepo, exportRepo, clearRepo } from "../utils.ts"; +import { clearRepo, exportRepo, testRepo } from "../utils.ts"; Deno.test("Get Repository Env", () => { exportRepo(testRepo); diff --git a/test/libs/redirect_test.ts b/test/libs/redirect_test.ts index 06d9c1e..12f9480 100644 --- a/test/libs/redirect_test.ts +++ b/test/libs/redirect_test.ts @@ -1,9 +1,9 @@ -import { testing, type RouterContext } from "@oak/oak"; +import { type RouterContext, testing } from "@oak/oak"; import { assertEquals } from "@std/assert"; import { STATUS_CODE } from "@std/http/status"; import { UserAgent } from "@std/http/user-agent"; import { redirect } from "../../src/libs/redirect.ts"; -import { testRepo, testRef, exportRepo } from "../utils.ts"; +import { exportRepo, testRef, testRepo } from "../utils.ts"; Deno.test("Redirect Detection", () => { const ctx: RouterContext = testing.createMockContext(); @@ -13,7 +13,7 @@ Deno.test("Redirect Detection", () => { assertEquals(ctx.response.status, STATUS_CODE.PermanentRedirect); assertEquals( ctx.response.headers.get("location"), - `https://github.com/${testRepo.owner}/${testRepo.name}/blob/master/${testRepo.path}` + `https://github.com/${testRepo.owner}/${testRepo.name}/blob/master/${testRepo.path}`, ); }); @@ -25,6 +25,6 @@ Deno.test("Redirect Detection (With ref)", () => { assertEquals(ctx.response.status, STATUS_CODE.PermanentRedirect); assertEquals( ctx.response.headers.get("location"), - `https://github.com/${testRepo.owner}/${testRepo.name}/blob/${testRef}/${testRepo.path}` + `https://github.com/${testRepo.owner}/${testRepo.name}/blob/${testRef}/${testRepo.path}`, ); }); diff --git a/test/router_test.ts b/test/router_test.ts index 59a8262..d520a21 100644 --- a/test/router_test.ts +++ b/test/router_test.ts @@ -1,8 +1,8 @@ -import { testing, type RouterContext } from "@oak/oak"; +import { type RouterContext, testing } from "@oak/oak"; import { assertEquals } from "@std/assert"; import { STATUS_CODE } from "@std/http/status"; import { router } from "../src/router.ts"; -import { testRepo, testRef, exportRepo } from "./utils.ts"; +import { exportRepo, testRef, testRepo } from "./utils.ts"; Deno.test("Serve (/)", async () => { const ctx: RouterContext = testing.createMockContext({ From 4cdb488fc9e5f49ffc12f2176d394b8edd6e76bf Mon Sep 17 00:00:00 2001 From: Souma <101255979+5ouma@users.noreply.github.com> Date: Wed, 12 Jun 2024 20:29:49 +0900 Subject: [PATCH 19/19] ci(test): Visualize code coverage (#6) Upload to Codecov and add comments to PRs. --- .github/README.md | 1 + .github/workflows/test.yml | 7 ++++++- .gitignore | 2 ++ deno.json | 3 ++- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/README.md b/.github/README.md index 7fc5945..0dccdba 100644 --- a/.github/README.md +++ b/.github/README.md @@ -12,6 +12,7 @@
[![Test](https://img.shields.io/github/actions/workflow/status/5ouma/reproxy/test.yml?label=test&style=flat-square)](https://github.com/5ouma/reproxy/actions/workflows/test.yml) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/5ouma/reproxy/main.svg?style=flat-square)](https://results.pre-commit.ci/latest/github/5ouma/reproxy/main) +[![codecov](https://codecov.io/github/5ouma/reproxy/graph/badge.svg?token=OQB55KXJIL)](https://codecov.io/github/5ouma/reproxy) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1209870..1a6f646 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,7 +29,7 @@ jobs: deno-version: v1.x - name: ๐Ÿงช Run Tests - run: deno task test + run: deno task cov - name: ๐Ÿงน Lint Check run: deno lint @@ -39,3 +39,8 @@ jobs: - name: ๐Ÿ” Type Check run: deno check ./**/*.ts + + - name: โ˜‚๏ธ Upload Coverage + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index f10862a..c457de3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /.env +/coverage.lcov +/coverage diff --git a/deno.json b/deno.json index 8227c40..c4f8c03 100644 --- a/deno.json +++ b/deno.json @@ -6,7 +6,8 @@ "run": "deno run --env='.env' --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH,GITHUB_TOKEN' --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='api.github.com' --parallel --shuffle" + "test": "deno test --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH,GITHUB_TOKEN' --allow-net='api.github.com' --parallel --shuffle", + "cov": "deno task test --coverage && deno coverage --lcov > coverage.lcov" }, "imports": { "@oak/oak": "jsr:@oak/oak@16.1.0",