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);