Move to Deno+Bunny.net, add a way to test webring locally.
This commit is contained in:
parent
9f66f0b37b
commit
8670aa3412
33 changed files with 599 additions and 3360 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -22,7 +22,7 @@ build/Release
|
|||
.env.local
|
||||
# Others
|
||||
.direnv/
|
||||
# Template rendering artifacts
|
||||
public/*.html
|
||||
# Build artifacts
|
||||
build/
|
||||
# Nix build result
|
||||
result
|
||||
|
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"deno.enable": true
|
||||
}
|
45
README.md
45
README.md
|
@ -1,19 +1,5 @@
|
|||
# Nixifying a new host
|
||||
|
||||
If you have a bunch of SSH keys in your SSH agent and get errors when trying to SSH into a fresh host, you may need to temporarily add the following config to your SSH config (obviously change the details for your case).
|
||||
The key is the line "IdentitiesOnly yes" along with an identity file.
|
||||
|
||||
```
|
||||
Host 188.245.194.78
|
||||
User root
|
||||
IdentityFile ~/.ssh/hetzner_personal_root.pub
|
||||
IdentitiesOnly yes
|
||||
```
|
||||
|
||||
# Adding yourself to the webring
|
||||
|
||||
(Please only follow this if you're an Epesooj insider. Random PRs will be closed for now.)
|
||||
|
||||
Add your information to `webring_data.json`.
|
||||
The schema is as follows:
|
||||
|
||||
|
@ -25,6 +11,11 @@ The schema is as follows:
|
|||
|
||||
Create a PR with your changes.
|
||||
|
||||
## Testing your entry
|
||||
|
||||
Run `deno task dev`, which should start a local web server.
|
||||
You can then navigate to it to inspect how your entry will look on the webring's main page, as well as make sure the url it points to works fine.
|
||||
|
||||
# Using the webring on your blog/page
|
||||
|
||||
Somewhere in your blog/page (footer recommended), add the following links.
|
||||
|
@ -37,3 +28,29 @@ Whenever your PR gets approved and merged, your id will be recognised and you'll
|
|||
- Previous entry: `https://akols.com/previous?id=<YOUR_ID_HERE>`
|
||||
- Next entry: `https://akols.com/next?id=<YOUR_ID_HERE>`
|
||||
- A link to the webring directory: `https://akols.com`
|
||||
|
||||
# Development setup
|
||||
|
||||
## Setting up VSCode (and flavours)
|
||||
|
||||
We're using [Deno](https://deno.com) in the webring functions, so if you want proper editor support, you'll need to install the [Deno extension](https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno).
|
||||
There's already a `.vscode` directory which should direct VSCode to enable the Deno extension for this project.
|
||||
|
||||
# Infrastructure
|
||||
|
||||
## Nixifying a new host
|
||||
|
||||
If you have a bunch of SSH keys in your SSH agent and get errors when trying to SSH into a fresh host, you may need to temporarily add the following config to your SSH config (obviously change the details for your case).
|
||||
The key is the line "IdentitiesOnly yes" along with an identity file.
|
||||
|
||||
```
|
||||
Host 188.245.194.78
|
||||
User root
|
||||
IdentityFile ~/.ssh/hetzner_personal_root.pub
|
||||
IdentitiesOnly yes
|
||||
```
|
||||
|
||||
## Deploying the webring
|
||||
|
||||
You should have a `.env` file with the id and deploy key for each script in the webring, as well as a key to deploy the index page to bunny.
|
||||
When you have this, run `deno task deploy`.
|
||||
|
|
16
bunny/webring.ts
Normal file
16
bunny/webring.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import * as BunnySDK from '@bunny.net/edgescript-sdk';
|
||||
import { handleFunctions } from '../src/main.ts';
|
||||
|
||||
BunnySDK.net.http
|
||||
.servePullZone({ url: 'https://akols.com' })
|
||||
.onOriginRequest(
|
||||
(ctx: { request: Request }): Promise<Request> | Promise<Response> => {
|
||||
const funcResp = handleFunctions(ctx.request);
|
||||
|
||||
if (funcResp !== undefined) {
|
||||
return Promise.resolve(funcResp);
|
||||
}
|
||||
|
||||
return Promise.resolve(ctx.request);
|
||||
}
|
||||
);
|
|
@ -111,7 +111,7 @@
|
|||
|
||||
globalConfig = ''
|
||||
# Comment this if building the prod image. The following is only useful for testing.
|
||||
local_certs
|
||||
# local_certs
|
||||
skip_install_trust
|
||||
'';
|
||||
|
||||
|
|
25
deno.json
Normal file
25
deno.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"tasks": {
|
||||
"dev": "deno run --watch --allow-read --allow-net src/main.ts",
|
||||
"clean": "rm -rf build",
|
||||
"build-index": {
|
||||
"command": "mkdir -p build && deno run --allow-read --allow-write scripts/build_index.ts",
|
||||
"dependencies": ["clean"]
|
||||
},
|
||||
"bundle": {
|
||||
"command": "mkdir -p build/bunny && deno run --allow-read --allow-write --allow-env --allow-run scripts/bundle_bunny.ts",
|
||||
"dependencies": ["clean"]
|
||||
},
|
||||
"deploy": {
|
||||
"command": "deno run --env-file --allow-env --allow-net --allow-read scripts/deploy_bunny.ts",
|
||||
"dependencies": ["clean", "build-index", "bundle"]
|
||||
}
|
||||
},
|
||||
"imports": {
|
||||
"@bunny.net/edgescript-sdk": "npm:@bunny.net/edgescript-sdk@^0.11.3",
|
||||
"@luca/esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@^0.11.1",
|
||||
"@std/assert": "jsr:@std/assert@1",
|
||||
"@std/path": "jsr:@std/path@^1.0.8",
|
||||
"esbuild": "npm:esbuild@^0.25"
|
||||
}
|
||||
}
|
185
deno.lock
generated
Normal file
185
deno.lock
generated
Normal file
|
@ -0,0 +1,185 @@
|
|||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@luca/esbuild-deno-loader@~0.11.1": "0.11.1",
|
||||
"jsr:@std/assert@1": "1.0.11",
|
||||
"jsr:@std/bytes@^1.0.2": "1.0.5",
|
||||
"jsr:@std/encoding@^1.0.5": "1.0.7",
|
||||
"jsr:@std/internal@^1.0.5": "1.0.5",
|
||||
"jsr:@std/path@^1.0.6": "1.0.8",
|
||||
"jsr:@std/path@^1.0.8": "1.0.8",
|
||||
"npm:@bunny.net/edgescript-sdk@~0.11.3": "0.11.3_hono@4.7.2",
|
||||
"npm:@types/node@*": "22.5.4",
|
||||
"npm:esbuild@*": "0.25.0",
|
||||
"npm:esbuild@0.25": "0.25.0"
|
||||
},
|
||||
"jsr": {
|
||||
"@luca/esbuild-deno-loader@0.11.1": {
|
||||
"integrity": "dc020d16d75b591f679f6b9288b10f38bdb4f24345edb2f5732affa1d9885267",
|
||||
"dependencies": [
|
||||
"jsr:@std/bytes",
|
||||
"jsr:@std/encoding",
|
||||
"jsr:@std/path@^1.0.6"
|
||||
]
|
||||
},
|
||||
"@std/assert@1.0.11": {
|
||||
"integrity": "2461ef3c368fe88bc60e186e7744a93112f16fd110022e113a0849e94d1c83c1",
|
||||
"dependencies": [
|
||||
"jsr:@std/internal"
|
||||
]
|
||||
},
|
||||
"@std/bytes@1.0.5": {
|
||||
"integrity": "4465dd739d7963d964c809202ebea6d5c6b8e3829ef25c6a224290fbb8a1021e"
|
||||
},
|
||||
"@std/encoding@1.0.7": {
|
||||
"integrity": "f631247c1698fef289f2de9e2a33d571e46133b38d042905e3eac3715030a82d"
|
||||
},
|
||||
"@std/internal@1.0.5": {
|
||||
"integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba"
|
||||
},
|
||||
"@std/path@1.0.8": {
|
||||
"integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"@bunny.net/edgescript-sdk@0.11.3_hono@4.7.2": {
|
||||
"integrity": "sha512-w7x/72NMR1Ofjs5DcntR35P6d2ApeBOD7LCe8s+e49bnKQKfehIOp6O9WCD2tgX9SJg06jSHBrfCEdJFNZMQyg==",
|
||||
"dependencies": [
|
||||
"@hono/node-server",
|
||||
"hono"
|
||||
]
|
||||
},
|
||||
"@esbuild/aix-ppc64@0.25.0": {
|
||||
"integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ=="
|
||||
},
|
||||
"@esbuild/android-arm64@0.25.0": {
|
||||
"integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g=="
|
||||
},
|
||||
"@esbuild/android-arm@0.25.0": {
|
||||
"integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g=="
|
||||
},
|
||||
"@esbuild/android-x64@0.25.0": {
|
||||
"integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg=="
|
||||
},
|
||||
"@esbuild/darwin-arm64@0.25.0": {
|
||||
"integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw=="
|
||||
},
|
||||
"@esbuild/darwin-x64@0.25.0": {
|
||||
"integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg=="
|
||||
},
|
||||
"@esbuild/freebsd-arm64@0.25.0": {
|
||||
"integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w=="
|
||||
},
|
||||
"@esbuild/freebsd-x64@0.25.0": {
|
||||
"integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A=="
|
||||
},
|
||||
"@esbuild/linux-arm64@0.25.0": {
|
||||
"integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg=="
|
||||
},
|
||||
"@esbuild/linux-arm@0.25.0": {
|
||||
"integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg=="
|
||||
},
|
||||
"@esbuild/linux-ia32@0.25.0": {
|
||||
"integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg=="
|
||||
},
|
||||
"@esbuild/linux-loong64@0.25.0": {
|
||||
"integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw=="
|
||||
},
|
||||
"@esbuild/linux-mips64el@0.25.0": {
|
||||
"integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ=="
|
||||
},
|
||||
"@esbuild/linux-ppc64@0.25.0": {
|
||||
"integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw=="
|
||||
},
|
||||
"@esbuild/linux-riscv64@0.25.0": {
|
||||
"integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA=="
|
||||
},
|
||||
"@esbuild/linux-s390x@0.25.0": {
|
||||
"integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA=="
|
||||
},
|
||||
"@esbuild/linux-x64@0.25.0": {
|
||||
"integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw=="
|
||||
},
|
||||
"@esbuild/netbsd-arm64@0.25.0": {
|
||||
"integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw=="
|
||||
},
|
||||
"@esbuild/netbsd-x64@0.25.0": {
|
||||
"integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA=="
|
||||
},
|
||||
"@esbuild/openbsd-arm64@0.25.0": {
|
||||
"integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw=="
|
||||
},
|
||||
"@esbuild/openbsd-x64@0.25.0": {
|
||||
"integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg=="
|
||||
},
|
||||
"@esbuild/sunos-x64@0.25.0": {
|
||||
"integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg=="
|
||||
},
|
||||
"@esbuild/win32-arm64@0.25.0": {
|
||||
"integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw=="
|
||||
},
|
||||
"@esbuild/win32-ia32@0.25.0": {
|
||||
"integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA=="
|
||||
},
|
||||
"@esbuild/win32-x64@0.25.0": {
|
||||
"integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ=="
|
||||
},
|
||||
"@hono/node-server@1.13.8_hono@4.7.2": {
|
||||
"integrity": "sha512-fsn8ucecsAXUoVxrUil0m13kOEq4mkX4/4QozCqmY+HpGfKl74OYSn8JcMA8GnG0ClfdRI4/ZSeG7zhFaVg+wg==",
|
||||
"dependencies": [
|
||||
"hono"
|
||||
]
|
||||
},
|
||||
"@types/node@22.5.4": {
|
||||
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
|
||||
"dependencies": [
|
||||
"undici-types"
|
||||
]
|
||||
},
|
||||
"esbuild@0.25.0": {
|
||||
"integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
|
||||
"dependencies": [
|
||||
"@esbuild/aix-ppc64",
|
||||
"@esbuild/android-arm",
|
||||
"@esbuild/android-arm64",
|
||||
"@esbuild/android-x64",
|
||||
"@esbuild/darwin-arm64",
|
||||
"@esbuild/darwin-x64",
|
||||
"@esbuild/freebsd-arm64",
|
||||
"@esbuild/freebsd-x64",
|
||||
"@esbuild/linux-arm",
|
||||
"@esbuild/linux-arm64",
|
||||
"@esbuild/linux-ia32",
|
||||
"@esbuild/linux-loong64",
|
||||
"@esbuild/linux-mips64el",
|
||||
"@esbuild/linux-ppc64",
|
||||
"@esbuild/linux-riscv64",
|
||||
"@esbuild/linux-s390x",
|
||||
"@esbuild/linux-x64",
|
||||
"@esbuild/netbsd-arm64",
|
||||
"@esbuild/netbsd-x64",
|
||||
"@esbuild/openbsd-arm64",
|
||||
"@esbuild/openbsd-x64",
|
||||
"@esbuild/sunos-x64",
|
||||
"@esbuild/win32-arm64",
|
||||
"@esbuild/win32-ia32",
|
||||
"@esbuild/win32-x64"
|
||||
]
|
||||
},
|
||||
"hono@4.7.2": {
|
||||
"integrity": "sha512-8V5XxoOF6SI12jkHkzX/6aLBMU5GEF5g387EjVSQipS0DlxWgWGSMeEayY3CRBjtTUQYwLHx9JYouWqKzy2Vng=="
|
||||
},
|
||||
"undici-types@6.19.8": {
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@luca/esbuild-deno-loader@~0.11.1",
|
||||
"jsr:@std/assert@1",
|
||||
"jsr:@std/path@^1.0.8",
|
||||
"npm:@bunny.net/edgescript-sdk@~0.11.3",
|
||||
"npm:esbuild@0.25"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -61,7 +61,7 @@
|
|||
default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
just
|
||||
nodePackages_latest.nodejs
|
||||
deno
|
||||
|
||||
colmena
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import { next } from '../src/webring';
|
||||
|
||||
export const onRequestGet: PagesFunction = async (context) => {
|
||||
const id = new URL(context.request.url).searchParams.get('id') ?? 'main';
|
||||
const item = next(id);
|
||||
const url = item?.url ?? new URL(context.request.url).origin;
|
||||
return Response.redirect(url, 303);
|
||||
};
|
|
@ -1,8 +0,0 @@
|
|||
import { previous } from '../src/webring';
|
||||
|
||||
export const onRequestGet: PagesFunction = async (context) => {
|
||||
const id = new URL(context.request.url).searchParams.get('id') ?? 'main';
|
||||
const item = previous(id);
|
||||
const url = item?.url ?? new URL(context.request.url).origin;
|
||||
return Response.redirect(url, 303);
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
import { randomEntry } from '../src/webring';
|
||||
|
||||
export const onRequestGet: PagesFunction = async (context) => {
|
||||
const url = randomEntry()?.url ?? new URL(context.request.url).origin;
|
||||
return Response.redirect(url, 303);
|
||||
};
|
3213
package-lock.json
generated
3213
package-lock.json
generated
File diff suppressed because it is too large
Load diff
22
package.json
22
package.json
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"name": "epesooj-webring",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"deploy": "wrangler deploy",
|
||||
"dev": "wrangler pages dev",
|
||||
"start": "wrangler pages dev",
|
||||
"test": "vitest",
|
||||
"cf-typegen": "wrangler types",
|
||||
"render-templates": "tsx src/render_templates.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/vitest-pool-workers": "^0.5.12",
|
||||
"@cloudflare/workers-types": "^4.20240925.0",
|
||||
"tsx": "^4.19.1",
|
||||
"typescript": "^5.6.2",
|
||||
"vitest": "^2.1.1",
|
||||
"wrangler": "^3.78.12"
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"version": 1,
|
||||
"include": ["/previous", "/random", "/next"],
|
||||
"exclude": ["/"]
|
||||
}
|
10
scripts/build_index.ts
Normal file
10
scripts/build_index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { join } from 'node:path';
|
||||
import { generateIndex } from '../src/render_templates.ts';
|
||||
|
||||
if (import.meta.main) {
|
||||
const encoder = new TextEncoder();
|
||||
Deno.writeFileSync(
|
||||
join(import.meta.dirname ?? '', '../build/index.html'),
|
||||
encoder.encode(generateIndex())
|
||||
);
|
||||
}
|
46
scripts/bundle_bunny.ts
Normal file
46
scripts/bundle_bunny.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import * as esbuild from 'esbuild';
|
||||
import { denoPlugins } from '@luca/esbuild-deno-loader';
|
||||
import { join } from '@std/path';
|
||||
|
||||
if (import.meta.main) {
|
||||
for (const dirEntry of Deno.readDirSync(
|
||||
join(import.meta.dirname ?? '', '../bunny')
|
||||
)) {
|
||||
if (dirEntry.isFile && dirEntry.name.endsWith('.ts')) {
|
||||
const outFilename =
|
||||
dirEntry.name.substring(0, dirEntry.name.length - 3) + '.js';
|
||||
|
||||
const res = await esbuild.build({
|
||||
plugins: [
|
||||
...denoPlugins({
|
||||
loader: 'native',
|
||||
}),
|
||||
],
|
||||
entryPoints: [
|
||||
join(import.meta.dirname ?? '', '../bunny', dirEntry.name),
|
||||
],
|
||||
outfile: join(import.meta.dirname ?? '', '../build/bunny', outFilename),
|
||||
bundle: true,
|
||||
format: 'esm',
|
||||
});
|
||||
|
||||
await esbuild.stop();
|
||||
|
||||
if (res.errors.length > 0) {
|
||||
console.error(
|
||||
`Got errors when trying to bundle ${dirEntry.name}: ${JSON.stringify(
|
||||
res.errors
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
if (res.warnings.length > 0) {
|
||||
console.warn(
|
||||
`Got warnings when trying to bundle ${
|
||||
dirEntry.name
|
||||
}: ${JSON.stringify(res.warnings)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
130
scripts/bunny_api/main.ts
Normal file
130
scripts/bunny_api/main.ts
Normal file
|
@ -0,0 +1,130 @@
|
|||
export async function deployScript(scriptName: string, contents: string) {
|
||||
const scriptIdEnvName = `SCRIPT_ID_${scriptName.toUpperCase()}`;
|
||||
const scriptId = Deno.env.get(scriptIdEnvName);
|
||||
const deployKeyEnvName = `SCRIPT_KEY_${scriptName.toUpperCase()}`;
|
||||
const deployKey = Deno.env.get(deployKeyEnvName);
|
||||
|
||||
if (scriptId === undefined) {
|
||||
throw new Error(
|
||||
`Can't deploy script '${scriptName}' because we don't know its id on bunny. Please set it by setting the environment variable '${scriptIdEnvName}'.`
|
||||
);
|
||||
}
|
||||
if (deployKey === undefined) {
|
||||
throw new Error(
|
||||
`Can't deploy script '${scriptName}' because we don't know its deploy key on bunny. Please set it by setting the environment variable '${scriptIdEnvName}'.`
|
||||
);
|
||||
}
|
||||
|
||||
const headers = {
|
||||
accept: 'application/json',
|
||||
'content-type': 'application/json',
|
||||
deploymentkey: deployKey,
|
||||
};
|
||||
|
||||
const setCodeResp = await fetch(
|
||||
`https://api.bunny.net/compute/script/${scriptId}/code`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ Code: contents }),
|
||||
}
|
||||
);
|
||||
|
||||
if (!setCodeResp.ok) {
|
||||
console.error(await setCodeResp.text());
|
||||
throw new Error(
|
||||
`Failed to upload new script code: ${setCodeResp.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
const publishResp = await fetch(
|
||||
`https://api.bunny.net/compute/script/${scriptId}/publish`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers,
|
||||
}
|
||||
);
|
||||
|
||||
if (!publishResp.ok) {
|
||||
console.error(await setCodeResp.text());
|
||||
throw new Error(`Failed to publish script: ${setCodeResp.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadFile(filepath: string, contents: Uint8Array) {
|
||||
const storageZoneNameEnvName = 'STORAGE_ZONE_NAME';
|
||||
const storageZoneName = Deno.env.get(storageZoneNameEnvName);
|
||||
const storageApiKeyEnvName = 'STORAGE_API_KEY';
|
||||
const apiKey = Deno.env.get(storageApiKeyEnvName);
|
||||
|
||||
if (storageZoneName === undefined) {
|
||||
throw new Error(
|
||||
`Can't upload file '${filepath}' because we don't know which storage zone to upload to. Please set it by setting the environment variable '${storageZoneNameEnvName}'.`
|
||||
);
|
||||
}
|
||||
|
||||
if (apiKey === undefined) {
|
||||
throw new Error(
|
||||
`Can't upload file '${filepath}' because we don't have an API key. Please set it by setting the environment variable '${storageApiKeyEnvName}'.`
|
||||
);
|
||||
}
|
||||
|
||||
const headers = {
|
||||
accept: 'application/json',
|
||||
'content-type': 'application/octet-stream',
|
||||
accesskey: apiKey,
|
||||
};
|
||||
|
||||
const res = await fetch(
|
||||
`https://storage.bunnycdn.com/${storageZoneName}/${filepath}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: contents,
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(await res.text());
|
||||
throw new Error(`Failed to upload file: ${res.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function purgePath(filepath: string) {
|
||||
const cdnBaseUrlEnvName = 'BUNNY_CDN_BASE_URL';
|
||||
const cdnBaseUrl = Deno.env.get(cdnBaseUrlEnvName);
|
||||
const accessKeyEnvName = 'BUNNY_ACCESS_KEY';
|
||||
const accessKey = Deno.env.get(accessKeyEnvName);
|
||||
|
||||
if (cdnBaseUrl === undefined) {
|
||||
throw new Error(
|
||||
`Can't purge cache for '${filepath}' because we don't know the CDN base URL. Please set it by setting the environment variable '${cdnBaseUrlEnvName}'.`
|
||||
);
|
||||
}
|
||||
|
||||
if (accessKey === undefined) {
|
||||
throw new Error(
|
||||
`Can't purge cache for '${filepath}' because we don't have an API key. Please set it by setting the environment variable '${accessKeyEnvName}'.`
|
||||
);
|
||||
}
|
||||
|
||||
const headers = {
|
||||
accept: 'application/json',
|
||||
'content-type': 'application/json',
|
||||
accesskey: accessKey,
|
||||
};
|
||||
|
||||
const fetchUrl = new URL(`https://api.bunny.net/purge`);
|
||||
fetchUrl.searchParams.append('async', 'false');
|
||||
fetchUrl.searchParams.append('url', `${cdnBaseUrl}/${filepath}`);
|
||||
|
||||
const res = await fetch(fetchUrl.toString(), {
|
||||
method: 'POST',
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(await res.text());
|
||||
throw new Error(`Failed to purge cache: ${res.statusText}`);
|
||||
}
|
||||
}
|
32
scripts/deploy_bunny.ts
Normal file
32
scripts/deploy_bunny.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { join } from '@std/path';
|
||||
import { deployScript, purgePath, uploadFile } from './bunny_api/main.ts';
|
||||
|
||||
if (import.meta.main) {
|
||||
console.log(`Attempting to upload index.html`);
|
||||
const localFilepath = join(import.meta.dirname ?? '', '../build/index.html');
|
||||
const indexContents = Deno.readFileSync(localFilepath);
|
||||
await uploadFile('index.html', indexContents);
|
||||
console.log(`Done!`);
|
||||
|
||||
console.log(`Attempting to purge the cache for index.html`);
|
||||
await purgePath('index.html');
|
||||
console.log(`Done!`);
|
||||
|
||||
for (const dirEntry of Deno.readDirSync(
|
||||
join(import.meta.dirname ?? '', '../build/bunny')
|
||||
)) {
|
||||
if (dirEntry.isFile && dirEntry.name.endsWith('.js')) {
|
||||
const scriptName = dirEntry.name.substring(0, dirEntry.name.length - 3);
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
const contents = decoder.decode(
|
||||
Deno.readFileSync(
|
||||
join(import.meta.dirname ?? '', '../build/bunny', dirEntry.name)
|
||||
)
|
||||
);
|
||||
|
||||
console.log(`Attempting to deploy script '${scriptName}'`);
|
||||
await deployScript(scriptName, contents);
|
||||
console.log(`Done!`);
|
||||
}
|
||||
}
|
||||
}
|
50
src/main.ts
Normal file
50
src/main.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { generateIndex } from './render_templates.ts';
|
||||
import { redirectNext } from './next.ts';
|
||||
import { redirectPrevious } from './previous.ts';
|
||||
import { redirectRandom } from './random.ts';
|
||||
|
||||
export function handleFunctions(req: Request) {
|
||||
const url = new URL(req.url);
|
||||
|
||||
switch (url.pathname) {
|
||||
case '/next': {
|
||||
return redirectNext(req);
|
||||
}
|
||||
|
||||
case '/previous': {
|
||||
return redirectPrevious(req);
|
||||
}
|
||||
|
||||
case '/random': {
|
||||
return redirectRandom(req);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
const index = generateIndex();
|
||||
|
||||
Deno.serve({ hostname: '127.0.0.1' }, (req) => {
|
||||
const funcResp = handleFunctions(req);
|
||||
|
||||
if (funcResp !== undefined) {
|
||||
return funcResp;
|
||||
}
|
||||
|
||||
const url = new URL(req.url);
|
||||
if (url.pathname === '/') {
|
||||
return new Response(index, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'content-type': 'text/html',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 404,
|
||||
});
|
||||
});
|
||||
}
|
15
src/next.ts
Normal file
15
src/next.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { next } from './webring.ts';
|
||||
|
||||
export function redirectNext(req: Request) {
|
||||
const id = new URL(req.url).searchParams.get('id') ?? 'main';
|
||||
const item = next(id);
|
||||
const url = item?.url ?? new URL(req.url).origin;
|
||||
|
||||
return new Response('', {
|
||||
status: 303,
|
||||
headers: {
|
||||
location: url,
|
||||
'cache-control': 'no-cache, no-store, no-transform',
|
||||
},
|
||||
});
|
||||
}
|
15
src/previous.ts
Normal file
15
src/previous.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { previous } from './webring.ts';
|
||||
|
||||
export function redirectPrevious(req: Request) {
|
||||
const id = new URL(req.url).searchParams.get('id') ?? 'main';
|
||||
const item = previous(id);
|
||||
const url = item?.url ?? new URL(req.url).origin;
|
||||
|
||||
return new Response('', {
|
||||
status: 303,
|
||||
headers: {
|
||||
location: url,
|
||||
'cache-control': 'no-cache, no-store, no-transform',
|
||||
},
|
||||
});
|
||||
}
|
13
src/random.ts
Normal file
13
src/random.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { randomEntry } from './webring.ts';
|
||||
|
||||
export function redirectRandom(req: Request) {
|
||||
const url = randomEntry()?.url ?? new URL(req.url).origin;
|
||||
|
||||
return new Response('', {
|
||||
status: 303,
|
||||
headers: {
|
||||
location: url,
|
||||
'cache-control': 'no-cache, no-store, no-transform',
|
||||
},
|
||||
});
|
||||
}
|
|
@ -1,15 +1,19 @@
|
|||
import { WebringData, WebringEntry } from './webring';
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { WebringData, WebringEntry } from './webring.ts';
|
||||
import { join } from '@std/path';
|
||||
|
||||
function generateEntryHTML(entry: WebringEntry) {
|
||||
return `<div><a href="${entry.url}">${entry.title}</a><span>by ${entry.author}</span></div>`;
|
||||
}
|
||||
|
||||
let index = readFileSync(
|
||||
join(import.meta.dirname, '../templates/index.html'),
|
||||
'utf8'
|
||||
);
|
||||
const renderedEntries = WebringData.map(generateEntryHTML).join('\n');
|
||||
index = index.replace('{{WEBRING_ENTRIES}}', renderedEntries);
|
||||
writeFileSync(join(import.meta.dirname, '../public/index.html'), index);
|
||||
export function generateIndex() {
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
let index = decoder.decode(
|
||||
Deno.readFileSync(
|
||||
join(import.meta.dirname ?? '', '../templates/index.html')
|
||||
)
|
||||
);
|
||||
const renderedEntries = WebringData.map(generateEntryHTML).join('\n');
|
||||
index = index.replace('{{WEBRING_ENTRIES}}', renderedEntries);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import WebringData from '../webring_data.json';
|
||||
import WebringData from '../webring_data.json' with { type: 'json' };
|
||||
export { WebringData };
|
||||
|
||||
export type WebringEntry = (typeof WebringData)[number];
|
||||
|
|
|
@ -54,13 +54,13 @@
|
|||
<a href="/next?id=main">Next site</a>
|
||||
</nav>
|
||||
|
||||
<h2>Epesooj webring</h2>
|
||||
<h1>Epesooj webring</h1>
|
||||
|
||||
<h2 id="webring-directory">Directory</h2>
|
||||
<nav class="directory" aria-labelledby="webring-directory">
|
||||
{{WEBRING_ENTRIES}}
|
||||
</nav>
|
||||
|
||||
<span class="source-link">The source for this webring can be viewed <a href="https://github.com/DanielSidhion/epesooj-webring">here</a>.</span>
|
||||
<span class="source-link">The source for this webring can be viewed <a href="https://code.akols.com/epesooj/webring">here</a>.</span>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('Hello World worker', () => {
|
||||
it('succeeds simple test', async () => {
|
||||
expect(1 + 1).toBe(2);
|
||||
});
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["@cloudflare/workers-types/experimental", "@cloudflare/vitest-pool-workers"]
|
||||
},
|
||||
"include": ["./**/*.ts", "../functions/env.d.ts"],
|
||||
"exclude": []
|
||||
}
|
7
test/webring_test.ts
Normal file
7
test/webring_test.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { assertEquals } from '@std/assert';
|
||||
import { next } from '../src/webring.ts';
|
||||
|
||||
Deno.test(function nextTest() {
|
||||
const nextEntry = next('main');
|
||||
assertEquals(nextEntry.id, 'dslog');
|
||||
});
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"lib": ["esnext"],
|
||||
"module": "esnext",
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["@cloudflare/workers-types/2023-07-01"],
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
"noEmit": true,
|
||||
"isolatedModules": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noUncheckedIndexedAccess": true
|
||||
},
|
||||
"exclude": ["test"],
|
||||
"include": ["worker-configuration.d.ts", "src/**/*.ts", "functions/**/*.ts"]
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
|
||||
|
||||
export default defineWorkersConfig({
|
||||
test: {
|
||||
poolOptions: {
|
||||
workers: {
|
||||
wrangler: { configPath: './wrangler.toml' },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
5
worker-configuration.d.ts
vendored
5
worker-configuration.d.ts
vendored
|
@ -1,5 +0,0 @@
|
|||
// Generated by Wrangler by running `wrangler types`
|
||||
|
||||
interface Env {
|
||||
NODE_VERSION: "22.8.0";
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#:schema node_modules/wrangler/config-schema.json
|
||||
name = "epesooj-webring"
|
||||
pages_build_output_dir = "./public"
|
||||
compatibility_date = "2024-09-27"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
|
||||
[vars]
|
||||
NODE_VERSION = "22.8.0"
|
Loading…
Add table
Reference in a new issue