diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index dd9e450..ef05e58 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -10,7 +10,6 @@ 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)
@@ -30,35 +29,6 @@ Body
-## ๐ 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 -- Merge --> main
-```
-
-
-
## โ Pull Requests Title
You don't need to add any prefixes like `feature` or `bug fix`
@@ -70,5 +40,5 @@ Please give a clear title.
## ๐ชต Commit Log
-I do squash merge to the dev branch to keep the commit history clean.
+I do squash merge to the main 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/branch-switcher.yml b/.github/branch-switcher.yml
deleted file mode 100644
index a4aaeea..0000000
--- a/.github/branch-switcher.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-preferredBranch: dev
-exclude: [branch: dev]
diff --git a/.github/delete-merged-branch-config.yml b/.github/delete-merged-branch-config.yml
deleted file mode 100644
index 2ded14b..0000000
--- a/.github/delete-merged-branch-config.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-exclude: [dev]
-delete_closed_pr: true
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 113875d..58bc340 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,5 +4,4 @@ updates:
directory: /
schedule:
interval: daily
- target-branch: dev
labels: []
diff --git a/.github/workflows/deps-update.yml b/.github/workflows/deps-update.yml
index 70bb511..22ac219 100644
--- a/.github/workflows/deps-update.yml
+++ b/.github/workflows/deps-update.yml
@@ -16,14 +16,11 @@ jobs:
steps:
- name: ๐ Checkout Repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- with:
- ref: dev
- name: ๐ฆ Update Dependencies
uses: hasundue/molt-action@2042116c4f16e14c08c98130f1470c19c5cbfd2f # v1.0.2
with:
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
- base: dev
branch: deps-deno
commit-prefix: "chore(deps):"
labels:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..f04828a
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,45 @@
+name: ๐ Release
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - deno.json
+
+permissions:
+ contents: write
+ id-token: write
+
+jobs:
+ Release:
+ runs-on: Ubuntu-Latest
+
+ steps:
+ - name: ๐ Checkout Repository
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+
+ - name: ๐ฆ Get the Package Version
+ uses: notiz-dev/github-action-json-property@a5a9c668b16513c737c3e1f8956772c99c73f6e8 # v0.2.0
+ id: version
+ with:
+ path: deno.json
+ prop_path: version
+
+ - name: ๐ Detect the Version has Changed
+ id: version-has-changed
+ run: |
+ tag="$(git tag -l | sort -V | tail -n 1)"
+ if [[ "$tag" == "v${{ steps.version.outputs.prop }}" ]]; then
+ echo "stop=true" | tee -a "$GITHUB_OUTPUT"
+ fi
+
+ - name: ๐ Rrelease a New Version
+ if: ${{ steps.version-has-changed.outputs.stop != 'true' }}
+ run: gh release create "v${{ steps.version.outputs.prop }}" --generate-notes
+ env:
+ GH_TOKEN: ${{ github.token }}
+
+ - name: โฌ๏ธ Publish to JSR
+ if: ${{ steps.version-has-changed.outputs.stop != 'true' }}
+ run: npx jsr publish
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index cd2120b..61710ae 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- - dev
paths:
- "**.ts"
- deno.lock
@@ -24,7 +23,7 @@ jobs:
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: ๐ฆ Setup Deno
- uses: denoland/setup-deno@5fae568d37c3b73449009674875529a984555dd1 # v1.3.0
+ uses: denoland/setup-deno@916edb9a40fd86d1ff57b758807ebe57242f7407 # v1.4.0
with:
deno-version: v1.x
@@ -34,6 +33,9 @@ jobs:
- name: ๐งน Lint Check
run: deno lint
+ - name: ๐ Lint the JSDoc
+ run: deno doc --lint ./src
+
- name: ๐ Format Check
run: deno fmt --check
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0346ed6..7bb61e5 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,7 +3,6 @@ ci:
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:
@@ -23,7 +22,7 @@ repos:
args: [--whitespaces-count, "2"]
- repo: https://github.com/crate-ci/typos
- rev: v1.23.6
+ rev: v1.24.1
hooks:
- id: typos
diff --git a/.github/README.md b/README.md
similarity index 75%
rename from .github/README.md
rename to README.md
index 0dccdba..fb73894 100644
--- a/.github/README.md
+++ b/README.md
@@ -5,12 +5,15 @@
**๐ 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)
+[![JSR](https://jsr.io/badges/@5ouma/reproxy?style=flat-square)](https://jsr.io/@5ouma/opmlreproxy)
+
![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)
+[![Release](https://img.shields.io/github/actions/workflow/status/5ouma/reproxy/release.yml?label=release&style=flat-square)](https://github.com/5ouma/reproxy/actions/workflows/release.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)
@@ -34,35 +37,30 @@ To do this, simply add the ref name to the sub-directory.
## ๐ง 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
- ```
+### ๐ป On Local
-2. Copy the [`.env.tmpl`](../.env.tmpl) to `.env` and edit as you prefer
+1. 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
+2. Run this command
```sh
- deployctl deploy --prod --env-file='.env'
+ deno run -A jsr:@5ouma/reproxy/serve
```
-### โ๏ธ Automatic Deployment
+### ๐ฆ Use [Deno Deploy](https://deno.com/deploy)
-1. [Fork this repository](https://github.com/5ouma/reproxy/fork)
+1. [Create a new playground](https://dash.deno.com)
-2. [Create a new project](https://dash.deno.com/new_project) with your forked repository
+2. Replace the default code with this
+
+ ```ts
+ import { app } from "jsr:@5ouma/reproxy";
+ Deno.serve(app.fetch);
+ ```
3. Set the environment variables
(_Don't forget!!_)
@@ -79,13 +77,11 @@ You can host your file on [Deno Deploy](https://deno.com/deploy).
git clone https://github.com/5ouma/reproxy.git
```
-2. Copy the [`.env.template`](../.env.template) to `.env` and edit as you prefer
+2. Copy the [`.env.tmpl`](./.env.tmpl) 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
+3. Run the [`server.ts`](./src/server.ts) via these task runners
```sh
# For production
@@ -103,10 +99,10 @@ You can host your file on [Deno Deploy](https://deno.com/deploy).
| Name | Required |
| :----------------: | :------: |
-| `REPOSITORY_OWNER` | true |
-| `REPOSITORY_NAME` | true |
-| `REPOSITORY_PATH` | true |
-| [`GITHUB_TOKEN`] | false |
+| `REPOSITORY_OWNER` | yes |
+| `REPOSITORY_NAME` | yes |
+| `REPOSITORY_PATH` | yes |
+| [`GITHUB_TOKEN`] | no |
> [!NOTE]
> You need to add [`GITHUB_TOKEN`] if you want to:
@@ -134,5 +130,5 @@ You can host your file on [Deno Deploy](https://deno.com/deploy).
I happily welcome your contributions!
Before you contribute,
-I would recommend reading [CONTRIBUTING.md](./CONTRIBUTING.md)
+I would recommend reading [CONTRIBUTING.md](./.github/CONTRIBUTING.md)
for a better development experience.
diff --git a/deno.json b/deno.json
index 3601d89..a928518 100644
--- a/deno.json
+++ b/deno.json
@@ -1,25 +1,24 @@
{
- "$schema": "https://deno.land/x/deno/cli/schemas/config-file.v1.json",
- "fmt": { "exclude": ["LICENSE", ".github/**/*.md"] },
+ "name": "@5ouma/reproxy",
+ "version": "1.0.0",
+ "exports": { ".": "./src/app.ts", "./serve": "./src/server.ts" },
+ "publish": {
+ "include": ["LICENSE", "README.md", "deno.json", "src/"],
+ "exclude": ["**/*.test.ts", "src/libs/test_utils.ts"]
+ },
+ "fmt": { "exclude": ["LICENSE", "README.md", ".github/**/*.md"] },
"tasks": {
"run": "deno run --env='.env' --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH,GITHUB_TOKEN' --allow-net='0.0.0.0,api.github.com'",
- "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",
+ "start": "deno task run src/server.ts",
+ "dev": "deno task run --watch src/server.ts",
+ "test": "deno test --allow-env='REPOSITORY_OWNER,REPOSITORY_NAME,REPOSITORY_PATH,GITHUB_TOKEN' --allow-net='0.0.0.0,api.github.com' --parallel --shuffle",
"cov": "deno task test --coverage && deno coverage --lcov > coverage.lcov"
},
"imports": {
- "@oak/oak": "jsr:@oak/oak@16.1.0",
- "@octokit/rest": "https://esm.sh/@octokit/rest@21.0.1",
+ "@hono/hono": "jsr:@hono/hono@4.5.8",
"@std/assert": "jsr:@std/assert@1.0.2",
- "@std/fmt": "jsr:@std/fmt@1.0.0",
"@std/http": "jsr:@std/http@1.0.2",
- "@std/http/user-agent": "jsr:@std/http@~0.223/user-agent",
- "@std/path": "jsr:@std/path@1.0.2"
- },
- "deploy": {
- "project": "reproxy",
- "include": ["./deno.json", "./src/"],
- "entrypoint": "./src/server.ts"
+ "@std/path": "jsr:@std/path@1.0.2",
+ "@octokit/rest": "npm:@octokit/rest@21.0.1"
}
}
diff --git a/deno.lock b/deno.lock
index 035607a..fc1639e 100644
--- a/deno.lock
+++ b/deno.lock
@@ -2,66 +2,23 @@
"version": "3",
"packages": {
"specifiers": {
- "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:@hono/hono@4.5.8": "jsr:@hono/hono@4.5.8",
"jsr:@std/assert@1.0.2": "jsr:@std/assert@1.0.2",
- "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/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.223": "jsr:@std/crypto@0.223.0",
- "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@1.0.0": "jsr:@std/fmt@1.0.0",
- "jsr:@std/http@0.223": "jsr:@std/http@0.223.0",
- "jsr:@std/http@0.224": "jsr:@std/http@0.224.5",
+ "jsr:@std/cli@^1.0.3": "jsr:@std/cli@1.0.4",
+ "jsr:@std/encoding@^1.0.1": "jsr:@std/encoding@1.0.3",
+ "jsr:@std/fmt@^1.0.0": "jsr:@std/fmt@1.0.1",
"jsr:@std/http@1.0.2": "jsr:@std/http@1.0.2",
- "jsr:@std/http@~0.223": "jsr:@std/http@0.223.0",
- "jsr:@std/internal@^1.0.1": "jsr:@std/internal@1.0.1",
- "jsr:@std/io@0.223": "jsr:@std/io@0.223.0",
- "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/internal@^1.0.1": "jsr:@std/internal@1.0.2",
+ "jsr:@std/media-types@^1.0.2": "jsr:@std/media-types@1.0.3",
+ "jsr:@std/net@^1.0.0": "jsr:@std/net@1.0.1",
"jsr:@std/path@1.0.2": "jsr:@std/path@1.0.2",
- "npm:path-to-regexp@6.2.1": "npm:path-to-regexp@6.2.1"
+ "jsr:@std/path@^1.0.2": "jsr:@std/path@1.0.2",
+ "jsr:@std/streams@^1.0.1": "jsr:@std/streams@1.0.3",
+ "npm:@octokit/rest@21.0.1": "npm:@octokit/rest@21.0.1_@octokit+core@6.1.2"
},
"jsr": {
- "@oak/commons@0.11.0": {
- "integrity": "07702bfe5c07cd8144c422022994da1f9fea466b185824f4be63a2b1b1a65125",
- "dependencies": [
- "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.1.0": {
- "integrity": "ab21506555fffeb08dc8f45ff5d28607e8087949ff58bd2964b27df65994480b",
- "dependencies": [
- "jsr:@oak/commons@0.11",
- "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.223.0": {
- "integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24"
- },
- "@std/assert@0.224.0": {
- "integrity": "8643233ec7aec38a940a8264a6e3eed9bfa44e7a71cc6b3c8874213ff401967f"
- },
- "@std/assert@0.226.0": {
- "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3"
+ "@hono/hono@4.5.8": {
+ "integrity": "60f5b4c61edae2016022d6087b4fc381f378d337f046f56e00a3a3512c7e9c16"
},
"@std/assert@1.0.2": {
"integrity": "ccacec332958126deaceb5c63ff8b4eaf9f5ed0eac9feccf124110435e59e49c",
@@ -69,108 +26,147 @@
"jsr:@std/internal@^1.0.1"
]
},
- "@std/bytes@0.223.0": {
- "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8"
- },
- "@std/bytes@0.224.0": {
- "integrity": "a2250e1d0eb7d1c5a426f21267ab9bdeac2447fa87a3d0d1a467d3f7a6058e49"
- },
- "@std/crypto@0.223.0": {
- "integrity": "1aa9555ff56b09e197ad988ea200f84bc6781fd4fd83f3a156ee44449af93000",
- "dependencies": [
- "jsr:@std/assert@^0.223.0",
- "jsr:@std/encoding@^0.223.0"
- ]
- },
- "@std/crypto@0.224.0": {
- "integrity": "154ef3ff08ef535562ef1a718718c5b2c5fc3808f0f9100daad69e829bfcdf2d",
- "dependencies": [
- "jsr:@std/assert@^0.224.0"
- ]
- },
- "@std/encoding@0.223.0": {
- "integrity": "2b5615a75e00337ce113f34cf2f9b8c18182c751a8dcc8b1a2c2fc0e117bef00"
- },
- "@std/encoding@1.0.0-rc.2": {
- "integrity": "160d7674a20ebfbccdf610b3801fee91cf6e42d1c106dd46bbaf46e395cd35ef"
+ "@std/cli@1.0.4": {
+ "integrity": "79ca75add572a99a8ba93ae37ccbd8d43fb4e2b635a8a7ebebb4f2d092048764"
},
- "@std/fmt@1.0.0": {
- "integrity": "8a95c9fdbb61559418ccbc0f536080cf43341655e1444f9d375a66886ceaaa3d"
+ "@std/encoding@1.0.3": {
+ "integrity": "5dbc2d7f5aa6062de7e19862ea856ac7a0dcce0b6fb46bb7b332d3bdcd4408b7"
},
- "@std/http@0.223.0": {
- "integrity": "15ab8a0c5a7e9d5be017a15b01600f20f66602ceec48b378939fa24fcec522aa",
- "dependencies": [
- "jsr:@std/assert@^0.223.0",
- "jsr:@std/encoding@^0.223.0"
- ]
- },
- "@std/http@0.224.5": {
- "integrity": "b03b5d1529f6c423badfb82f6640f9f2557b4034cd7c30655ba5bb447ff750a4",
- "dependencies": [
- "jsr:@std/encoding@1.0.0-rc.2"
- ]
+ "@std/fmt@1.0.1": {
+ "integrity": "ef76c37faa7720faa8c20fd8cc74583f9b1e356dfd630c8714baa716a45856ab"
},
"@std/http@1.0.2": {
- "integrity": "e28d612e2b5e0a40704e8c64d9a5d55cf8c6accf6d798cd1cfb43aad02d1e4fe"
- },
- "@std/internal@1.0.1": {
- "integrity": "6f8c7544d06a11dd256c8d6ba54b11ed870aac6c5aeafff499892662c57673e6"
- },
- "@std/io@0.223.0": {
- "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1",
+ "integrity": "e28d612e2b5e0a40704e8c64d9a5d55cf8c6accf6d798cd1cfb43aad02d1e4fe",
"dependencies": [
- "jsr:@std/bytes@^0.223.0"
+ "jsr:@std/cli@^1.0.3",
+ "jsr:@std/encoding@^1.0.1",
+ "jsr:@std/fmt@^1.0.0",
+ "jsr:@std/media-types@^1.0.2",
+ "jsr:@std/net@^1.0.0",
+ "jsr:@std/path@^1.0.2",
+ "jsr:@std/streams@^1.0.1"
]
},
- "@std/media-types@0.223.0": {
- "integrity": "84684680c2eb6bc6d9369c6d6f26a49decaf2c7603ff531862dda575d9d6776e"
+ "@std/internal@1.0.2": {
+ "integrity": "f4cabe2021352e8bfc24e6569700df87bf070914fc38d4b23eddd20108ac4495"
},
- "@std/media-types@0.224.1": {
- "integrity": "9e69a5daed37c5b5c6d3ce4731dc191f80e67f79bed392b0957d1d03b87f11e1"
+ "@std/media-types@1.0.3": {
+ "integrity": "b12d30a7852f7578f4d210622df713bbfd1cbdd9b4ec2eaf5c1845ab70bab159"
},
- "@std/path@0.223.0": {
- "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989",
- "dependencies": [
- "jsr:@std/assert@^0.223.0"
- ]
+ "@std/net@1.0.1": {
+ "integrity": "27fe136ab7ae81f425df90aad190bae2c87d2aef3d1a49cbc0efc1daa287d794"
},
"@std/path@1.0.2": {
"integrity": "a452174603f8c620bd278a380c596437a9eef50c891c64b85812f735245d9ec7"
+ },
+ "@std/streams@1.0.3": {
+ "integrity": "d62e645ab981cee2c3d03040eb03cf387fc6bceef6d4564f3ed485a43741a81f"
}
},
"npm": {
- "path-to-regexp@6.2.1": {
- "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==",
+ "@octokit/auth-token@5.1.1": {
+ "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==",
+ "dependencies": {}
+ },
+ "@octokit/core@6.1.2": {
+ "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==",
+ "dependencies": {
+ "@octokit/auth-token": "@octokit/auth-token@5.1.1",
+ "@octokit/graphql": "@octokit/graphql@8.1.1",
+ "@octokit/request": "@octokit/request@9.1.3",
+ "@octokit/request-error": "@octokit/request-error@6.1.4",
+ "@octokit/types": "@octokit/types@13.5.0",
+ "before-after-hook": "before-after-hook@3.0.2",
+ "universal-user-agent": "universal-user-agent@7.0.2"
+ }
+ },
+ "@octokit/endpoint@10.1.1": {
+ "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
+ "dependencies": {
+ "@octokit/types": "@octokit/types@13.5.0",
+ "universal-user-agent": "universal-user-agent@7.0.2"
+ }
+ },
+ "@octokit/graphql@8.1.1": {
+ "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==",
+ "dependencies": {
+ "@octokit/request": "@octokit/request@9.1.3",
+ "@octokit/types": "@octokit/types@13.5.0",
+ "universal-user-agent": "universal-user-agent@7.0.2"
+ }
+ },
+ "@octokit/openapi-types@22.2.0": {
+ "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==",
+ "dependencies": {}
+ },
+ "@octokit/plugin-paginate-rest@11.3.3_@octokit+core@6.1.2": {
+ "integrity": "sha512-o4WRoOJZlKqEEgj+i9CpcmnByvtzoUYC6I8PD2SA95M+BJ2x8h7oLcVOg9qcowWXBOdcTRsMZiwvM3EyLm9AfA==",
+ "dependencies": {
+ "@octokit/core": "@octokit/core@6.1.2",
+ "@octokit/types": "@octokit/types@13.5.0"
+ }
+ },
+ "@octokit/plugin-request-log@5.3.1_@octokit+core@6.1.2": {
+ "integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==",
+ "dependencies": {
+ "@octokit/core": "@octokit/core@6.1.2"
+ }
+ },
+ "@octokit/plugin-rest-endpoint-methods@13.2.4_@octokit+core@6.1.2": {
+ "integrity": "sha512-gusyAVgTrPiuXOdfqOySMDztQHv6928PQ3E4dqVGEtOvRXAKRbJR4b1zQyniIT9waqaWk/UDaoJ2dyPr7Bk7Iw==",
+ "dependencies": {
+ "@octokit/core": "@octokit/core@6.1.2",
+ "@octokit/types": "@octokit/types@13.5.0"
+ }
+ },
+ "@octokit/request-error@6.1.4": {
+ "integrity": "sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg==",
+ "dependencies": {
+ "@octokit/types": "@octokit/types@13.5.0"
+ }
+ },
+ "@octokit/request@9.1.3": {
+ "integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
+ "dependencies": {
+ "@octokit/endpoint": "@octokit/endpoint@10.1.1",
+ "@octokit/request-error": "@octokit/request-error@6.1.4",
+ "@octokit/types": "@octokit/types@13.5.0",
+ "universal-user-agent": "universal-user-agent@7.0.2"
+ }
+ },
+ "@octokit/rest@21.0.1_@octokit+core@6.1.2": {
+ "integrity": "sha512-RWA6YU4CqK0h0J6tfYlUFnH3+YgBADlxaHXaKSG+BVr2y4PTfbU2tlKuaQoQZ83qaTbi4CUxLNAmbAqR93A6mQ==",
+ "dependencies": {
+ "@octokit/core": "@octokit/core@6.1.2",
+ "@octokit/plugin-paginate-rest": "@octokit/plugin-paginate-rest@11.3.3_@octokit+core@6.1.2",
+ "@octokit/plugin-request-log": "@octokit/plugin-request-log@5.3.1_@octokit+core@6.1.2",
+ "@octokit/plugin-rest-endpoint-methods": "@octokit/plugin-rest-endpoint-methods@13.2.4_@octokit+core@6.1.2"
+ }
+ },
+ "@octokit/types@13.5.0": {
+ "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==",
+ "dependencies": {
+ "@octokit/openapi-types": "@octokit/openapi-types@22.2.0"
+ }
+ },
+ "before-after-hook@3.0.2": {
+ "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==",
+ "dependencies": {}
+ },
+ "universal-user-agent@7.0.2": {
+ "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==",
"dependencies": {}
}
}
},
- "remote": {
- "https://esm.sh/@octokit/rest@21.0.1": "f46bfa9d68ef9c77b0c65f19523d73f2e1512f0b563920dcdb56ecc099f2ce24",
- "https://esm.sh/v135/@octokit/auth-token@5.1.0/denonext/auth-token.mjs": "24c22015e586d621821b0acb1b5b82d32c95d1813d264ce8fa3f769ba8806e8c",
- "https://esm.sh/v135/@octokit/core@6.1.2/denonext/core.mjs": "2629f0f304f0a8313644ed4ddedbf1c7fbce06e80aadc6f718f6a4b4f401f9d7",
- "https://esm.sh/v135/@octokit/endpoint@10.0.0/denonext/endpoint.mjs": "8d6f16f0b272fc7a20582637a3ffbd29c14f4bf629fafda05c33cf214b0dd716",
- "https://esm.sh/v135/@octokit/endpoint@10.1.0/denonext/endpoint.mjs": "cb307fd4d62518987144d31210f929e67d644c7bcad866cdfde547a528d48e12",
- "https://esm.sh/v135/@octokit/graphql@8.1.0/denonext/graphql.mjs": "a84701c7a52ca92d4b201f7502c3b048bfb3c0be9a131ed04bc736ccab160aba",
- "https://esm.sh/v135/@octokit/plugin-paginate-rest@11.3.3/denonext/plugin-paginate-rest.mjs": "c82a962edb386933cf0a112d972d5349fa50a2a2401ec472f6292dba403ad218",
- "https://esm.sh/v135/@octokit/plugin-request-log@5.3.1/denonext/plugin-request-log.mjs": "f9e3386dbcedf6c635812f004f2681f82402254f091c9753f694eb64790fabfe",
- "https://esm.sh/v135/@octokit/plugin-rest-endpoint-methods@13.2.4/denonext/plugin-rest-endpoint-methods.mjs": "28cb28ddc2367113e9134415a9611be9be6210cdd9f5dc210f3bd9d49f41302d",
- "https://esm.sh/v135/@octokit/request-error@6.0.2/denonext/request-error.mjs": "d25f73ba0f75f4ed9830caa96bffc7e36a73e51c0777caa8f436af3ede1cb116",
- "https://esm.sh/v135/@octokit/request-error@6.1.0/denonext/request-error.mjs": "271a5a47ff4c05bf414443880c68fcc4c75e4da7c0e2caab1a4998fe4b6961b3",
- "https://esm.sh/v135/@octokit/request@9.0.1/denonext/request.mjs": "150c8b8f650bcb2567a3e30906871234b1f449cd3765af418d15e08767bea449",
- "https://esm.sh/v135/@octokit/request@9.1.0/denonext/request.mjs": "cc64353d55ef0f89428c9d752e57adad51e8dc7e03884f6086f8b953f781ae3e",
- "https://esm.sh/v135/@octokit/rest@21.0.1/denonext/rest.mjs": "d9c2ff439438e475c71118f715f3afb62e8b372e7c0a859fddf2e81216b80751",
- "https://esm.sh/v135/before-after-hook@3.0.2/denonext/before-after-hook.mjs": "cf2363bb9be543fadd912087b89d461082fd2ee66a9a3cc81fbeeea11aea8c32",
- "https://esm.sh/v135/universal-user-agent@7.0.2/denonext/universal-user-agent.mjs": "c95431a8f6a7593e78b18ba137d1ae8fb8e2c7ebbcfc55c3d2cf1c9667ae8554"
- },
+ "remote": {},
"workspace": {
"dependencies": [
- "jsr:@oak/oak@16.1.0",
+ "jsr:@hono/hono@4.5.8",
"jsr:@std/assert@1.0.2",
- "jsr:@std/fmt@1.0.0",
"jsr:@std/http@1.0.2",
- "jsr:@std/http@~0.223",
- "jsr:@std/path@1.0.2"
+ "jsr:@std/path@1.0.2",
+ "npm:@octokit/rest@21.0.1"
]
}
}
diff --git a/src/app.test.ts b/src/app.test.ts
new file mode 100644
index 0000000..2fd146c
--- /dev/null
+++ b/src/app.test.ts
@@ -0,0 +1,61 @@
+import { assertEquals } from "@std/assert";
+import { STATUS_CODE } from "@std/http/status";
+
+import { app } from "./app.ts";
+import {
+ exportRepo,
+ testRef,
+ testRepo,
+ testUserAgent,
+} from "./libs/test_utils.ts";
+import { getGitHubUrl } from "./libs/utils.ts";
+
+Deno.test("Serve", async (t: Deno.TestContext) => {
+ await t.step("/", async () => {
+ exportRepo(testRepo);
+ const res: Response = await app.request("/");
+
+ assertEquals(res.status, STATUS_CODE.OK);
+ });
+
+ await t.step("/ (Redirect)", async () => {
+ exportRepo(testRepo);
+ const res: Response = await app.request("/", {
+ headers: { "User-Agent": testUserAgent.toString() },
+ });
+
+ assertEquals(
+ res.headers.get("Location"),
+ getGitHubUrl(testRepo).toString(),
+ );
+ assertEquals(res.status, STATUS_CODE.PermanentRedirect);
+ });
+
+ await t.step("/:ref", async () => {
+ exportRepo(testRepo);
+ const res: Response = await app.request(`/${testRef}`);
+
+ assertEquals(res.status, STATUS_CODE.OK);
+ });
+
+ await t.step("/:ref (Redirect)", async () => {
+ exportRepo(testRepo);
+ const res: Response = await app.request(`/${testRef}`, {
+ headers: { "User-Agent": testUserAgent.toString() },
+ });
+
+ assertEquals(
+ res.headers.get("Location"),
+ getGitHubUrl(testRepo, testRef).toString(),
+ );
+ assertEquals(res.status, STATUS_CODE.PermanentRedirect);
+ });
+
+ await t.step("*", async () => {
+ exportRepo(testRepo);
+ const res: Response = await app.request("/anything/else");
+
+ assertEquals(res.headers.get("Location"), "/");
+ assertEquals(res.status, STATUS_CODE.SeeOther);
+ });
+});
diff --git a/src/app.ts b/src/app.ts
new file mode 100644
index 0000000..3ec021c
--- /dev/null
+++ b/src/app.ts
@@ -0,0 +1,37 @@
+import { type Context, Hono } from "@hono/hono";
+export type { Hono };
+import { STATUS_CODE } from "@std/http/status";
+import { UserAgent } from "@std/http/user-agent";
+
+import { checkRedirect, getContent } from "./libs/mod.ts";
+
+/**
+ * The Hono application for this project.
+ *
+ * @example Access without a user agent
+ * ```ts
+ * const res: Response = await app.request("/");
+ * ```
+ * @example Access with a user agent
+ * ```ts
+ * const res: Response = await app.request("/", {
+ * headers: { "User-Agent": new UserAgent("Chrome/1.2.3").toString() },
+ * });
+ * ```
+ */
+export const app: Hono = new Hono();
+app
+ .get("/:ref?", async (ctx: Context) => {
+ const ref: string = ctx.req.param("ref");
+ const url: URL | null = checkRedirect(
+ new UserAgent(ctx.req.header("User-Agent") ?? ""),
+ ref,
+ );
+
+ return url
+ ? ctx.redirect(url.toString(), STATUS_CODE.PermanentRedirect)
+ : ctx.text(...await getContent(ref));
+ })
+ .get("*", (ctx: Context) => {
+ return ctx.redirect("/", STATUS_CODE.SeeOther);
+ });
diff --git a/src/libs/content.test.ts b/src/libs/content.test.ts
new file mode 100644
index 0000000..fe05eba
--- /dev/null
+++ b/src/libs/content.test.ts
@@ -0,0 +1,31 @@
+import { assertEquals, assertExists, assertStringIncludes } from "@std/assert";
+import { STATUS_CODE } from "@std/http/status";
+
+import { getContent } from "./content.ts";
+import { exportRepo, testRef, testRepo, unknownRepo } from "./test_utils.ts";
+
+Deno.test("Get Content", async (t: Deno.TestContext) => {
+ await t.step("normal", async () => {
+ exportRepo(testRepo);
+ const [data, status] = await getContent();
+
+ assertExists(data);
+ assertEquals(status, STATUS_CODE.OK);
+ });
+
+ await t.step("with ref", async () => {
+ exportRepo(testRepo);
+ const [data, status] = await getContent(testRef);
+
+ assertExists(data);
+ assertEquals(status, STATUS_CODE.OK);
+ });
+
+ await t.step("not found", async () => {
+ exportRepo(unknownRepo);
+ const [data, status] = await getContent();
+
+ assertStringIncludes(data, `โ ๏ธ ${STATUS_CODE.NotFound}:`);
+ assertEquals(status, STATUS_CODE.NotFound);
+ });
+});
diff --git a/src/libs/content.ts b/src/libs/content.ts
new file mode 100644
index 0000000..bdaaf74
--- /dev/null
+++ b/src/libs/content.ts
@@ -0,0 +1,52 @@
+import { Octokit } from "@octokit/rest";
+import type { StatusCode } from "@std/http";
+export type { StatusCode };
+
+import { getRepository, githubToken } from "./env.ts";
+
+/**
+ * Get the content of the repository.
+ *
+ * @param ref The name of the branch, tag or commit hash
+ * @returns The content of the repository and the status code
+ *
+ * @example Use the default branch
+ * ```ts
+ * const [content, status] = await getContent();
+ * ```
+ * @example Use a specific branch
+ * ```ts
+ * const branch = "main";
+ * const [content, status] = await getContent(branch);
+ * ```
+ * @example Use a specific tag
+ * ```ts
+ * const tag = "v1.0.0";
+ * const [content, status] = await getContent(tag);
+ * ```
+ * @example Use a specific commit
+ * ```ts
+ * const commit = "a1b2c3d4e5f6";
+ * const [content, status] = await getContent(commit);
+ * ```
+ */
+export async function getContent(
+ ref: string | undefined = undefined,
+): Promise<[string, StatusCode]> {
+ const octokit = new Octokit({ auth: githubToken });
+ const repository = getRepository();
+
+ try {
+ const { status, data } = await octokit.rest.repos.getContent({
+ mediaType: { format: "raw" },
+ owner: repository.owner,
+ repo: repository.name,
+ path: repository.path,
+ ref: ref,
+ });
+
+ return [data.toString(), status];
+ } catch (error) {
+ return [`โ ๏ธ ${error.status}: ${error.message}`, error.status];
+ }
+}
diff --git a/src/libs/utils/env_test.ts b/src/libs/env.test.ts
similarity index 90%
rename from src/libs/utils/env_test.ts
rename to src/libs/env.test.ts
index 33c72f0..0a1d47c 100644
--- a/src/libs/utils/env_test.ts
+++ b/src/libs/env.test.ts
@@ -1,7 +1,7 @@
import { assertEquals } from "@std/assert";
import { getRepository } from "./env.ts";
-import { clearRepo, exportRepo, testRepo } from "../test_utils.ts";
+import { clearRepo, exportRepo, testRepo } from "./test_utils.ts";
Deno.test("Get Repository Env", async (t: Deno.TestContext) => {
await t.step("Normal", () => {
diff --git a/src/libs/utils/env.ts b/src/libs/env.ts
similarity index 58%
rename from src/libs/utils/env.ts
rename to src/libs/env.ts
index 56b33b0..2016570 100644
--- a/src/libs/utils/env.ts
+++ b/src/libs/env.ts
@@ -1,5 +1,15 @@
-import type { Repository } from "../types.ts";
+import type { Repository } from "./types.ts";
+/**
+ * Get the repository details from the environment variables.
+ *
+ * @returns {Repository} The repository type
+ *
+ * @example
+ * ```ts
+ * const repository = getRepository();
+ * ```
+ */
export function getRepository(): Repository {
const repository: Repository = {
owner: Deno.env.get("REPOSITORY_OWNER") ?? "",
@@ -16,4 +26,12 @@ export function getRepository(): Repository {
return repository;
}
+/**
+ * The GitHub token from the environment variables.
+ *
+ * @example
+ * ```ts
+ * const token = githubToken;
+ * ```
+ */
export const githubToken: string | undefined = Deno.env.get("GITHUB_TOKEN");
diff --git a/src/libs/middlewares/content.ts b/src/libs/middlewares/content.ts
deleted file mode 100644
index 8fdbd43..0000000
--- a/src/libs/middlewares/content.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import type { RouterContext } from "@oak/oak";
-import { Octokit } from "@octokit/rest";
-
-import { getRepository, githubToken } from "../utils/env.ts";
-
-export async function getContent(
- ctx: RouterContext,
- ref: string | undefined = undefined,
-): Promise {
- const octokit = new Octokit({ auth: githubToken });
- const repository = getRepository();
-
- try {
- const { status, data } = await octokit.rest.repos.getContent({
- mediaType: { format: "raw" },
- owner: repository.owner,
- repo: repository.name,
- path: repository.path,
- ref: ref,
- });
-
- 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/middlewares/content_test.ts b/src/libs/middlewares/content_test.ts
deleted file mode 100644
index b065e8f..0000000
--- a/src/libs/middlewares/content_test.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { type RouterContext, testing } from "@oak/oak";
-import { assertEquals, assertStringIncludes } from "@std/assert";
-import { STATUS_CODE } from "@std/http/status";
-
-import { getContent } from "./content.ts";
-import { exportRepo, testRef, testRepo, unknownRepo } from "../test_utils.ts";
-
-Deno.test("Get Content", async (t: Deno.TestContext) => {
- const ctx: RouterContext = testing.createMockContext();
-
- await t.step("normal", async () => {
- exportRepo(testRepo);
- await getContent(ctx);
-
- assertEquals(ctx.response.status, STATUS_CODE.OK);
- });
-
- await t.step("with ref", async () => {
- exportRepo(testRepo);
- await getContent(ctx, testRef);
-
- assertEquals(ctx.response.status, STATUS_CODE.OK);
- });
-
- await t.step("not found", async () => {
- exportRepo(unknownRepo);
- await getContent(ctx);
-
- assertEquals(ctx.response.status, STATUS_CODE.NotFound);
- assertStringIncludes(
- ctx.response.body!.toString(),
- `โ ๏ธ ${STATUS_CODE.NotFound}:`,
- );
- });
-});
diff --git a/src/libs/middlewares/redirect.ts b/src/libs/middlewares/redirect.ts
deleted file mode 100644
index 956ac11..0000000
--- a/src/libs/middlewares/redirect.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-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 "../utils/env.ts";
-
-export function redirect(
- ctx: RouterContext,
- userAgent: UserAgent,
- ref: string = "master",
-): boolean {
- const repository = getRepository();
- const url = new URL(
- join(
- repository.owner,
- repository.name,
- "blob",
- ref,
- repository.path,
- ),
- "https://github.com",
- );
-
- if (!userAgent?.browser.name) return false;
-
- ctx.response.status = STATUS_CODE.PermanentRedirect;
- ctx.response.redirect(url);
- return true;
-}
diff --git a/src/libs/middlewares/redirect_test.ts b/src/libs/middlewares/redirect_test.ts
deleted file mode 100644
index cb945fb..0000000
--- a/src/libs/middlewares/redirect_test.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-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 "./redirect.ts";
-import { exportRepo, testRef, testRepo } from "../test_utils.ts";
-
-Deno.test("Redirect Detection", async (t: Deno.TestContext) => {
- const ctx: RouterContext = testing.createMockContext();
- exportRepo(testRepo);
-
- await t.step("normal", () => {
- redirect(ctx, new UserAgent("Chrome/1.2.3"));
-
- assertEquals(ctx.response.status, STATUS_CODE.PermanentRedirect);
- assertEquals(
- ctx.response.headers.get("location"),
- `https://github.com/${testRepo.owner}/${testRepo.name}/blob/master/${testRepo.path}`,
- );
- });
-
- await t.step("with ref", () => {
- 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/${testRepo.owner}/${testRepo.name}/blob/${testRef}/${testRepo.path}`,
- );
- });
-});
diff --git a/src/libs/mod.ts b/src/libs/mod.ts
index a7769af..7311f3e 100644
--- a/src/libs/mod.ts
+++ b/src/libs/mod.ts
@@ -1,2 +1,2 @@
-export * from "./middlewares/content.ts";
-export * from "./middlewares/redirect.ts";
+export * from "./content.ts";
+export * from "./redirect.ts";
diff --git a/src/libs/redirect.test.ts b/src/libs/redirect.test.ts
new file mode 100644
index 0000000..0054c3c
--- /dev/null
+++ b/src/libs/redirect.test.ts
@@ -0,0 +1,21 @@
+import { assertEquals } from "@std/assert";
+
+import { checkRedirect } from "./redirect.ts";
+import { exportRepo, testRef, testRepo, testUserAgent } from "./test_utils.ts";
+import { getGitHubUrl } from "./utils.ts";
+
+Deno.test("Redirect Detection", async (t: Deno.TestContext) => {
+ await t.step("normal", () => {
+ exportRepo(testRepo);
+ const url: URL | null = checkRedirect(testUserAgent);
+
+ assertEquals(url, getGitHubUrl(testRepo));
+ });
+
+ await t.step("with ref", () => {
+ exportRepo(testRepo);
+ const url: URL | null = checkRedirect(testUserAgent, testRef);
+
+ assertEquals(url, getGitHubUrl(testRepo, testRef));
+ });
+});
diff --git a/src/libs/redirect.ts b/src/libs/redirect.ts
new file mode 100644
index 0000000..386837a
--- /dev/null
+++ b/src/libs/redirect.ts
@@ -0,0 +1,45 @@
+import type { UserAgent } from "@std/http/user-agent";
+export type { UserAgent };
+
+import { getRepository } from "./env.ts";
+import { getGitHubUrl } from "./utils.ts";
+
+/**
+ * Check if accessed from a browser and return the GitHub URL.
+ *
+ * @param userAgent The user agent accessed from
+ * @param ref="master" The branch, tag, or commit hash
+ * @returns The GitHub repository URL or null
+ *
+ * @example Use the default branch
+ * ```ts
+ * const userAgent = new UserAgent("Chrome/1.2.3");
+ * const url: URL | null = checkRedirect(userAgent);
+ * ```
+ * @example Use a specific branch
+ * ```ts
+ * const userAgent = new UserAgent("Chrome/1.2.3");
+ * const branch = "main";
+ * const url: URL | null = checkRedirect(userAgent, branch);
+ * ```
+ * @example Use a specific tag
+ * ```ts
+ * const userAgent = new UserAgent("Chrome/1.2.3");
+ * const tag = "v1.0.0";
+ * const url: URL | null = checkRedirect(userAgent, tag);
+ * ```
+ * @example Use a specific commit
+ * ```ts
+ * const userAgent = new UserAgent("Chrome/1.2.3");
+ * const commit = "a1b2c3d4e5f6";
+ * const url: URL | null = checkRedirect(userAgent, commit);
+ * ```
+ */
+export function checkRedirect(
+ userAgent: UserAgent,
+ ref: string = "master",
+): URL | null {
+ const url = getGitHubUrl(getRepository(), ref);
+
+ return userAgent?.browser.name ? url : null;
+}
diff --git a/src/libs/test_utils.ts b/src/libs/test_utils.ts
index 4362005..fe55859 100644
--- a/src/libs/test_utils.ts
+++ b/src/libs/test_utils.ts
@@ -1,25 +1,80 @@
+import { UserAgent } from "@std/http/user-agent";
+
import type { Repository } from "./types.ts";
+/**
+ * Sample repository for test.
+ *
+ * @example
+ * ```ts
+ * const repository = testRepo;
+ * ```
+ */
export const testRepo: Repository = {
owner: "denoland",
name: "deno",
path: "README.md",
};
+/**
+ * Sample unknown repository for test.
+ *
+ * @example
+ * ```ts
+ * const repository = unknownRepo;
+ * ```
+ */
export const unknownRepo: Repository = {
owner: "unknown-owner",
name: "unknown-repo",
path: "unknown-path",
};
+/**
+ * Sample version reference for test.
+ *
+ * @example
+ * ```ts
+ * const ref = testRef;
+ * ```
+ */
export const testRef = "v1.0.0";
+/**
+ * Sample user agent for test.
+ *
+ * @example
+ * ```ts
+ * const userAgent = testUserAgent;
+ * ```
+ */
+export const testUserAgent = new UserAgent("Chrome/1.2.3");
+
+/**
+ * Export the repository details to the environment variables.
+ *
+ * @param repository The repository type
+ *
+ * @example
+ * ```ts
+ * const repository = new Repository("5ouma", "reproxy", "src/server.ts");
+ * exportRepo(repository);
+ * ```
+ */
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);
}
+/**
+ * Clear the repository details from the environment variables.
+ *
+ * @example
+ * ```ts
+ * clearRepo();
+ * ```
+ */
export function clearRepo() {
Deno.env.delete("REPOSITORY_OWNER");
Deno.env.delete("REPOSITORY_NAME");
diff --git a/src/libs/types.ts b/src/libs/types.ts
index d2dfbcf..3575123 100644
--- a/src/libs/types.ts
+++ b/src/libs/types.ts
@@ -1,3 +1,19 @@
+/**
+ * Repository type.
+ *
+ * @property owner The repository owner
+ * @property name The repository name
+ * @property path The content path from the root of the repository
+ *
+ * @example
+ * ```ts
+ * const repository: Repository = {
+ * owner: "5ouma",
+ * name: "reproxy",
+ * path: "src/server.ts",
+ * };
+ * ```
+ */
export type Repository = {
owner: string;
name: string;
diff --git a/src/libs/utils.ts b/src/libs/utils.ts
new file mode 100644
index 0000000..a619489
--- /dev/null
+++ b/src/libs/utils.ts
@@ -0,0 +1,36 @@
+import { join } from "@std/path";
+
+import type { Repository } from "./types.ts";
+
+/**
+ * Get the GitHub URL of the repository.
+ *
+ * @param repository The repository type
+ * @param ref="master" The reference to use
+ * @returns The GitHub repository URL
+ *
+ * @example
+ * ```ts
+ * const repository: Repository = {
+ * owner: "5ouma",
+ * name: "reproxy",
+ * path: "src/server.ts",
+ * };
+ * const url: URL = getGitHubUrl(repository);
+ * ```
+ */
+export function getGitHubUrl(
+ repository: Repository,
+ ref: string = "master",
+): URL {
+ return new URL(
+ join(
+ repository.owner,
+ repository.name,
+ "blob",
+ ref,
+ repository.path,
+ ),
+ "https://github.com",
+ );
+}
diff --git a/src/router.ts b/src/router.ts
deleted file mode 100644
index a9f7f51..0000000
--- a/src/router.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-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";
- if (!redirect(ctx, ctx.request.userAgent)) await getContent(ctx);
- })
- .get("/:ref", async (ctx: RouterContext) => {
- const ref: string | undefined = ctx.params.ref;
- ctx.response.type = "text/plain";
- if (!redirect(ctx, ctx.request.userAgent, ref)) await getContent(ctx, ref);
- });
diff --git a/src/router_test.ts b/src/router_test.ts
deleted file mode 100644
index 3f01af3..0000000
--- a/src/router_test.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { type RouterContext, testing } from "@oak/oak";
-import { assertEquals } from "@std/assert";
-import { STATUS_CODE } from "@std/http/status";
-
-import { router } from "./router.ts";
-import { exportRepo, testRef, testRepo } from "./libs/test_utils.ts";
-
-Deno.test("Serve", async (t: Deno.TestContext) => {
- await t.step("/", async () => {
- const ctx: RouterContext = testing.createMockContext({
- method: "GET",
- path: "/",
- });
- exportRepo(testRepo);
- await router.routes()(ctx, () => Promise.resolve());
-
- assertEquals(ctx.response.status, STATUS_CODE.OK);
- });
-
- await t.step("/:ref", async () => {
- const ctx: RouterContext = testing.createMockContext({
- method: "GET",
- path: `/${testRef}`,
- });
- exportRepo(testRepo);
- await router.routes()(ctx, () => Promise.resolve());
-
- assertEquals(ctx.response.status, STATUS_CODE.OK);
- });
-});
diff --git a/src/server.ts b/src/server.ts
index b5089f2..add4720 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -1,25 +1,8 @@
-import { Application } from "@oak/oak";
-import type { ApplicationListenEvent } from "@oak/oak/application";
-import { yellow } from "@std/fmt/colors";
+/**
+ * This file is the entry point for the server.
+ * @module
+ */
-import { router } from "./router.ts";
+import { app } from "./app.ts";
-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 });
+Deno.serve(app.fetch);