ci: enforce oxc checks for worker

This commit is contained in:
Peter Steinberger 2026-04-30 17:54:44 +01:00
parent b4cd356443
commit b528a765fc
No known key found for this signature in database
15 changed files with 899 additions and 44 deletions

View File

@ -70,6 +70,14 @@ jobs:
run: npm ci
working-directory: worker
- name: Format
run: npm run format:check
working-directory: worker
- name: Lint
run: npm run lint
working-directory: worker
- name: Typecheck
run: npm run check
working-directory: worker

View File

@ -97,6 +97,8 @@ Local checks:
```sh
npm ci --prefix worker
npm run format:check --prefix worker
npm run lint --prefix worker
npm run check --prefix worker
npm test --prefix worker
npm run build --prefix worker
@ -178,6 +180,8 @@ go vet ./...
go test -race ./...
go build -trimpath -o bin/crabbox ./cmd/crabbox
npm ci --prefix worker
npm run format:check --prefix worker
npm run lint --prefix worker
npm run check --prefix worker
npm test --prefix worker
npm run build --prefix worker

16
worker/.oxfmtrc.json Normal file
View File

@ -0,0 +1,16 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"arrowParens": "always",
"bracketSpacing": true,
"endOfLine": "lf",
"ignorePatterns": ["dist/**", "node_modules/**"],
"insertFinalNewline": true,
"printWidth": 100,
"semi": true,
"singleQuote": false,
"sortImports": true,
"sortPackageJson": true,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false
}

22
worker/.oxlintrc.json Normal file
View File

@ -0,0 +1,22 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"categories": {
"correctness": "deny",
"perf": "deny",
"suspicious": "deny"
},
"env": {
"builtin": true,
"es2026": true,
"node": true,
"vitest": true,
"worker": true
},
"ignorePatterns": ["dist/**", "node_modules/**"],
"options": {
"denyWarnings": true,
"maxWarnings": 0,
"reportUnusedDisableDirectives": "deny"
},
"plugins": ["typescript", "unicorn", "import", "vitest", "promise", "node"]
}

791
worker/package-lock.json generated
View File

@ -9,6 +9,8 @@
"version": "0.1.0",
"devDependencies": {
"@cloudflare/workers-types": "^4.20260430.0",
"oxfmt": "^0.47.0",
"oxlint": "^1.62.0",
"typescript": "^5.9.3",
"vitest": "^4.0.10",
"wrangler": "^4.50.0"
@ -1216,6 +1218,700 @@
"url": "https://github.com/sponsors/Boshen"
}
},
"node_modules/@oxfmt/binding-android-arm-eabi": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.47.0.tgz",
"integrity": "sha512-KrMQRdMi/upr81qT4ijK6X6BNp6jqpMY7FwILQnwIy9QLc3qpnhUx5rsCLGzn4ewsCQ0CNAspN2ogmP1GXLyLw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-android-arm64": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.47.0.tgz",
"integrity": "sha512-r4ixS/PeUpAFKgrpDoZ5pSkthjZzVzKd95525Aazj+aOv9H4ulK5zYHGb7wFY5n5kZxHK8TbOJUZgoEb1ohddQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-darwin-arm64": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.47.0.tgz",
"integrity": "sha512-CLWxiKpMl+195cm09CuaWEhJK0CirRkoMa07aR9+9AFPat2LfIKtwx1JqxZM0MTvcMe6+adlJNdVL6jdInvq3g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-darwin-x64": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.47.0.tgz",
"integrity": "sha512-Xq5fjTYDC50faUeLSm0rZdBqoTgleXEdD7NpJdARtQIczkCJn3xNjMUSQQkUmh4CtxkKTNL68lytcOK3e/osgg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-freebsd-x64": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.47.0.tgz",
"integrity": "sha512-QOU9ZIJ52p5askcEC0QJvvr8trHAWoonul8bgISo6gYUL3s50zkqafBYcNAr9LJZQbsZtPfIWHk9+5+nUp1qJQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-linux-arm-gnueabihf": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.47.0.tgz",
"integrity": "sha512-oJxDM1aBhPvz9gmElBv8UpxyiqhwfjcbrSxT5F0xtuUzY6dQI27/AQPIt3eu3Z5Yvn0kQl5R7MA3Z+MbnRvCBw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-linux-arm-musleabihf": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.47.0.tgz",
"integrity": "sha512-g8Lh50VS4ibGz2q6v7r9UZY4D0dM16SdrFYOMzhqIoCwGcai8VMIRUAcqn1/jlCsOOzUXJ741+kCeJt0cofakQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-linux-arm64-gnu": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.47.0.tgz",
"integrity": "sha512-YrNT1vQ0asaXoRbrvYENPqmBfOQ9Xr8enPNOULeYfg44VjCcrUowFy5QZr+WawE0zyP8cH9e9Gxxg0fDEFzhcg==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-linux-arm64-musl": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.47.0.tgz",
"integrity": "sha512-IxtQC/sbBi4ubbY+MdwdanRWrG9InQJVZqyMsBa5IUaQcnSg86gQme574HxXMC1p4bo4YhV99zQ+wNnGCvEgzw==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-linux-ppc64-gnu": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.47.0.tgz",
"integrity": "sha512-EWXEhOMbWO0q6eJSbu0QLkU8cKi0ljlYLngeDs2Ocu/pm1rrLwyQiYzlFbdnMRURI4w9ndr1sI9rSbhlJ5o23Q==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-linux-riscv64-gnu": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.47.0.tgz",
"integrity": "sha512-tZrjS11TUiDuEpRaqdk8K9F9xETRyKXfuZKmdeW+Gj7coBnm7+8sBEfyt033EAFEQSlkniAXvBLh+Qja2ioGBQ==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-linux-riscv64-musl": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.47.0.tgz",
"integrity": "sha512-KBFy+2CFKUCZzYwX2ZOPQKck1vjQbz+hextuc19G4r0WRJwadfAeuQMQRQvB+Ivc8brlbOVg7et8K7E467440g==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-linux-s390x-gnu": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.47.0.tgz",
"integrity": "sha512-REUPFKVGSiK99B+9eaPhluEVglzaoj/SMykNC5SUiV2RSsBfV5lWN7Y0iCIc251Wz3GaeAGZsJ/zj3gjarxdFg==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-linux-x64-gnu": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.47.0.tgz",
"integrity": "sha512-KVftVSVEDeIfRW3TIeLe3aNI/iY4m1fu5mDwHcisKMZSCMKLkrhFsjowC7o9RoqNPxbbglm2+/6KAKBIts2t0Q==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-linux-x64-musl": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.47.0.tgz",
"integrity": "sha512-DTsmGEaA2860Aq5VUyDO8/MT9NFxwVL93RnRYmpMwK6DsSkThmvEpqoUDDljziEpAedMRG19SCogrNbINSbLUQ==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-openharmony-arm64": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.47.0.tgz",
"integrity": "sha512-8r5BDro7fLOBoq1JXHLVSs55OlrxQhEso4HVo0TcY7OXJUPYfjPoOaYL5us+yIwqyP9rQwN+rxuiNFSmaxSuOQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-win32-arm64-msvc": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.47.0.tgz",
"integrity": "sha512-qtz/gzm8IjSPUlseZ0ofW8zyHLoZsuP5HTfcGGkWkUblB89JT8GNYH3ICqjbDsqsGqXum0/ZndXTFplSdXFIcg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-win32-ia32-msvc": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.47.0.tgz",
"integrity": "sha512-5vIcdcIDE7nCx+MXN6sm8kbC4zajDB31E86rez4i45iHNH/2NjdKlJ720xcHTr3eeiMcttCGPHPhE1TjtBDGZw==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxfmt/binding-win32-x64-msvc": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.47.0.tgz",
"integrity": "sha512-Sr59Y5ms54ONBjxFeWhVlGyQcHXxcl9DxC23f6yXlRkcos7LXBLoO+KDfxexjHIOZh7cWqrWduzvUjJ+pHp8cQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-android-arm-eabi": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.62.0.tgz",
"integrity": "sha512-pKsthNECyvJh8lPTICz6VcwVy2jOqdhhsp1rlxCkhgZR47aKvXPmaRWQDv+zlXpRae4qm1MaaTnutkaOk5aofg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-android-arm64": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.62.0.tgz",
"integrity": "sha512-b1AUNViByvgmR2xJDubvLIr+dSuu3uraG7bsAoKo+xrpspPvu6RIn6Fhr2JUhobfep3jwUTy18Huco6GkwdvGQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-darwin-arm64": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.62.0.tgz",
"integrity": "sha512-iG+Tvf70UJ6otfwFYIHk36Sjq9cpPP5YLxkoggANNRtzgi3Tj3g8q6Ybqi6AtkU3+yg9QwF7bDCkCS6bbL4PCg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-darwin-x64": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.62.0.tgz",
"integrity": "sha512-oOWI6YPPr5AJUx+yIDlxmuUbQjS5gZX3OH3QisawYvsZgLiQVvZtR0rPBcJTxLWqt2ClrWg0DlSrlUiG5SQNHg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-freebsd-x64": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.62.0.tgz",
"integrity": "sha512-dLP33T7VLCmLVv4cvjkVX+rmkcwNk2UfxmsZPNur/7BQHoQR60zJ7XLiRvNUawlzn0u8ngCa3itjEG73MAMa/w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-linux-arm-gnueabihf": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.62.0.tgz",
"integrity": "sha512-fl//LWNks6qo9chNY60UDYyIwtp7a5cEx4Y/rHPjaarhuwqx6jtbzEpD5V5AqmdL4a6Y5D8zeXg5HF2Cr0QmSQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-linux-arm-musleabihf": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.62.0.tgz",
"integrity": "sha512-i5vkAuxvueTODV3J2dL61/TXewDHhMFKvtD156cIsk7GsdfiAu7zW7kY0NJXhKeFHeiMZIh7eFNjkPYH6J47HQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-linux-arm64-gnu": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.62.0.tgz",
"integrity": "sha512-QwN19LLuIGuOjEflSeJkZmOTfBdBMlTmW8xbMf8TZhjd//cxVNYQPq75q7oKZBJc6hRx3gY7sX0Egc8cEIFZYg==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-linux-arm64-musl": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.62.0.tgz",
"integrity": "sha512-8eCy3FCDuWUM5hWujAv6heMvfZPbcCOU3SdQUAkixZLu5bSzOkNfirJiLGoQFO943xceOKkiQRMQNzH++jM3WA==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-linux-ppc64-gnu": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.62.0.tgz",
"integrity": "sha512-NjQ7K7tpTPDe9J+yq8p/s/J0E7lRCkK2uDBDqvT4XIT6f4Z0tlnr59OBg/WcrmVHER1AbrcfyxhGTXgcG8ytWg==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-linux-riscv64-gnu": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.62.0.tgz",
"integrity": "sha512-oKZed9gmSwze29dEt3/Wnsv6l/Ygw/FUst+8Kfpv2SGeS/glEoTGZAMQw37SVyzFV76UTHJN2snGgxK2t2+8ow==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-linux-riscv64-musl": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.62.0.tgz",
"integrity": "sha512-gBjBxQ+9lGpAYq+ELqw0w8QXsBnkZclFc7GRX2r0LnEVn3ZTEqeIKpKcGjucmp76Q53bvJD0i4qBWBhcfhSfGA==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-linux-s390x-gnu": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.62.0.tgz",
"integrity": "sha512-Ew2Kxs9EQ9/mbAIJ2hvocMC0wsOu6YKzStI2eFBDt+Td5O8seVC/oxgRIHqCcl5sf5ratA1nozQBAuv7tphkHg==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-linux-x64-gnu": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.62.0.tgz",
"integrity": "sha512-5z25jcAA0gfKyVwz71A0VXgaPlocPoTAxhlv/hgoK6tlCrfoNuw7haWbDHvGMfjXhdic4EqVXGRv5XsTqFnbRQ==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-linux-x64-musl": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.62.0.tgz",
"integrity": "sha512-IWpHmMB6ZDllPvqWDkG6AmXrN7JF5e/c4g/0PuURsmlK+vHoYZPB70rr4u1bn3I4LsKCSpqqfveyx6UCOC8wdg==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-openharmony-arm64": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.62.0.tgz",
"integrity": "sha512-fjlSxxrD5pA594vkyikCS9MnPRjQawW6/BLgyTYkO+73wwPlYjkcZ7LSd974l0Q2zkHQmu4DPvJFLYA7o8xrxQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-win32-arm64-msvc": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.62.0.tgz",
"integrity": "sha512-EiFXr8loNS0Ul3Gu80+9nr1T8jRmnKocqmHHg16tj5ZqTgUXyb97l2rrspVHdDluyFn9JfR4PoJFdNzw4paHww==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-win32-ia32-msvc": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.62.0.tgz",
"integrity": "sha512-IgOFvL73li1bFgab+hThXYA0N2Xms2kV2MvZN95cebV+fmrZ9AVui1JSxfeeqRLo3CpPxKZlzhyq4G0cnaAvIw==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint/binding-win32-x64-msvc": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.62.0.tgz",
"integrity": "sha512-6hMpyDWQ2zGA1OXFKBrdYMUveUCO8UJhkO6JdwZPd78xIdHZNhjx+pib+4fC2Cljuhjyl0QwA2F3df/bs4Bp6A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@poppinss/colors": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz",
@ -2217,6 +2913,91 @@
],
"license": "MIT"
},
"node_modules/oxfmt": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/oxfmt/-/oxfmt-0.47.0.tgz",
"integrity": "sha512-OFbkbzxKCpooQEnRmpTDnuwTX8KHXzZTQ4Df/hz85fpS67Pl+lxPEFvUtin56HIIS0B1k4X8oIzTXRZPufA2CA==",
"dev": true,
"license": "MIT",
"dependencies": {
"tinypool": "2.1.0"
},
"bin": {
"oxfmt": "bin/oxfmt"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/sponsors/Boshen"
},
"optionalDependencies": {
"@oxfmt/binding-android-arm-eabi": "0.47.0",
"@oxfmt/binding-android-arm64": "0.47.0",
"@oxfmt/binding-darwin-arm64": "0.47.0",
"@oxfmt/binding-darwin-x64": "0.47.0",
"@oxfmt/binding-freebsd-x64": "0.47.0",
"@oxfmt/binding-linux-arm-gnueabihf": "0.47.0",
"@oxfmt/binding-linux-arm-musleabihf": "0.47.0",
"@oxfmt/binding-linux-arm64-gnu": "0.47.0",
"@oxfmt/binding-linux-arm64-musl": "0.47.0",
"@oxfmt/binding-linux-ppc64-gnu": "0.47.0",
"@oxfmt/binding-linux-riscv64-gnu": "0.47.0",
"@oxfmt/binding-linux-riscv64-musl": "0.47.0",
"@oxfmt/binding-linux-s390x-gnu": "0.47.0",
"@oxfmt/binding-linux-x64-gnu": "0.47.0",
"@oxfmt/binding-linux-x64-musl": "0.47.0",
"@oxfmt/binding-openharmony-arm64": "0.47.0",
"@oxfmt/binding-win32-arm64-msvc": "0.47.0",
"@oxfmt/binding-win32-ia32-msvc": "0.47.0",
"@oxfmt/binding-win32-x64-msvc": "0.47.0"
}
},
"node_modules/oxlint": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.62.0.tgz",
"integrity": "sha512-1uFkg6HakjsGIpW9wNdeW4/2LOHW9MEkoWjZUTUfQtIHyLIZPYt00w3Sg+H3lH+206FgBPHBbW5dVE5l2ExECQ==",
"dev": true,
"license": "MIT",
"bin": {
"oxlint": "bin/oxlint"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/sponsors/Boshen"
},
"optionalDependencies": {
"@oxlint/binding-android-arm-eabi": "1.62.0",
"@oxlint/binding-android-arm64": "1.62.0",
"@oxlint/binding-darwin-arm64": "1.62.0",
"@oxlint/binding-darwin-x64": "1.62.0",
"@oxlint/binding-freebsd-x64": "1.62.0",
"@oxlint/binding-linux-arm-gnueabihf": "1.62.0",
"@oxlint/binding-linux-arm-musleabihf": "1.62.0",
"@oxlint/binding-linux-arm64-gnu": "1.62.0",
"@oxlint/binding-linux-arm64-musl": "1.62.0",
"@oxlint/binding-linux-ppc64-gnu": "1.62.0",
"@oxlint/binding-linux-riscv64-gnu": "1.62.0",
"@oxlint/binding-linux-riscv64-musl": "1.62.0",
"@oxlint/binding-linux-s390x-gnu": "1.62.0",
"@oxlint/binding-linux-x64-gnu": "1.62.0",
"@oxlint/binding-linux-x64-musl": "1.62.0",
"@oxlint/binding-openharmony-arm64": "1.62.0",
"@oxlint/binding-win32-arm64-msvc": "1.62.0",
"@oxlint/binding-win32-ia32-msvc": "1.62.0",
"@oxlint/binding-win32-x64-msvc": "1.62.0"
},
"peerDependencies": {
"oxlint-tsgolint": ">=0.18.0"
},
"peerDependenciesMeta": {
"oxlint-tsgolint": {
"optional": true
}
}
},
"node_modules/path-to-regexp": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
@ -2450,6 +3231,16 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tinypool": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.0.tgz",
"integrity": "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^20.0.0 || >=22.0.0"
}
},
"node_modules/tinyrainbow": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",

View File

@ -7,10 +7,15 @@
"build": "wrangler deploy --dry-run --outdir dist",
"check": "tsc --noEmit",
"deploy": "wrangler deploy",
"format": "oxfmt --write .",
"format:check": "oxfmt --check .",
"lint": "oxlint --config .oxlintrc.json .",
"test": "vitest run"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20260430.0",
"oxfmt": "^0.47.0",
"oxlint": "^1.62.0",
"typescript": "^5.9.3",
"vitest": "^4.0.10",
"wrangler": "^4.50.0"

View File

@ -35,7 +35,7 @@ export function leaseConfig(input: LeaseRequest): LeaseConfig {
workRoot: input.workRoot ?? "/work/crabbox",
ttlSeconds,
keep: input.keep ?? false,
sshPublicKey
sshPublicKey,
};
}

View File

@ -1,6 +1,6 @@
import { leaseConfig } from "./config";
import { errorMessage, json, pathParts, readJson, requestOwner } from "./http";
import { HetznerClient } from "./hetzner";
import { errorMessage, json, pathParts, readJson, requestOwner } from "./http";
import type { Env, LeaseRecord, LeaseRequest } from "./types";
const fleetID = "default";
@ -8,7 +8,7 @@ const fleetID = "default";
export class FleetDurableObject implements DurableObject {
constructor(
private readonly state: DurableObjectState,
private readonly env: Env
private readonly env: Env,
) {}
async fetch(request: Request): Promise<Response> {
@ -65,7 +65,7 @@ export class FleetDurableObject implements DurableObject {
state: "active",
createdAt: now.toISOString(),
updatedAt: now.toISOString(),
expiresAt: new Date(now.getTime() + config.ttlSeconds * 1000).toISOString()
expiresAt: new Date(now.getTime() + config.ttlSeconds * 1000).toISOString(),
};
await this.putLease(record);
await this.scheduleAlarm();
@ -118,17 +118,19 @@ export class FleetDurableObject implements DurableObject {
const leases = await this.state.storage.list<LeaseRecord>({ prefix: "lease:" });
const now = Date.now();
const client = new HetznerClient(this.env);
for (const lease of leases.values()) {
if (lease.state !== "active" || Date.parse(lease.expiresAt) > now) {
continue;
}
if (!lease.keep) {
await client.deleteServer(lease.serverID).catch(() => undefined);
}
lease.state = "expired";
lease.updatedAt = new Date().toISOString();
await this.putLease(lease);
}
const expired = [...leases.values()].filter(
(lease) => lease.state === "active" && Date.parse(lease.expiresAt) <= now,
);
await Promise.all(
expired.map(async (lease) => {
if (!lease.keep) {
await client.deleteServer(lease.serverID).catch(() => undefined);
}
lease.state = "expired";
lease.updatedAt = new Date().toISOString();
await this.putLease(lease);
}),
);
}
private async scheduleAlarm(): Promise<void> {

View File

@ -31,7 +31,7 @@ export class HetznerClient {
async listCrabboxServers(): Promise<HetznerServer[]> {
const query = new URLSearchParams({
label_selector: "crabbox=true",
per_page: "100"
per_page: "100",
});
const response = await this.request<HetznerListServersResponse>("GET", `/servers?${query}`);
return response.servers;
@ -40,7 +40,7 @@ export class HetznerClient {
async ensureSSHKey(name: string, publicKey: string): Promise<HetznerSSHKey> {
const byName = await this.request<HetznerListSSHKeysResponse>(
"GET",
`/ssh_keys?${new URLSearchParams({ name })}`
`/ssh_keys?${new URLSearchParams({ name })}`,
);
for (const key of byName.ssh_keys) {
if (key.name === name) {
@ -53,7 +53,7 @@ export class HetznerClient {
const all = await this.request<HetznerListSSHKeysResponse>(
"GET",
`/ssh_keys?${new URLSearchParams({ per_page: "100" })}`
`/ssh_keys?${new URLSearchParams({ per_page: "100" })}`,
);
for (const key of all.ssh_keys) {
if (key.public_key.trim() === publicKey.trim()) {
@ -66,8 +66,8 @@ export class HetznerClient {
public_key: publicKey,
labels: {
crabbox: "true",
created_by: "crabbox"
}
created_by: "crabbox",
},
});
return created.ssh_key;
}
@ -75,17 +75,18 @@ export class HetznerClient {
async createServerWithFallback(
config: LeaseConfig,
leaseID: string,
owner: string
owner: string,
): Promise<{ server: HetznerServer; serverType: string }> {
const key = await this.ensureSSHKey(config.providerKey, config.sshPublicKey);
const resolvedConfig = { ...config, providerKey: key.name };
const candidates = prependUnique(
resolvedConfig.serverType,
serverTypeCandidatesForClass(resolvedConfig.class)
serverTypeCandidatesForClass(resolvedConfig.class),
);
const failures: string[] = [];
for (const serverType of candidates) {
try {
// oxlint-disable-next-line eslint/no-await-in-loop -- server-type fallback must stay sequential.
const server = await this.createServer({ ...resolvedConfig, serverType }, leaseID, owner);
return { server, serverType };
} catch (error) {
@ -106,10 +107,12 @@ export class HetznerClient {
async waitForServerIP(id: number): Promise<HetznerServer> {
const deadline = Date.now() + 60_000;
while (Date.now() < deadline) {
// oxlint-disable-next-line eslint/no-await-in-loop -- polling must wait between Hetzner API reads.
const server = await this.getServer(id);
if (server.public_net.ipv4.ip) {
return server;
}
// oxlint-disable-next-line eslint/no-await-in-loop -- this delay is the polling interval.
await sleep(2_000);
}
throw new Error(`timed out waiting for server IP: ${id}`);
@ -126,14 +129,14 @@ export class HetznerClient {
status: server.status,
serverType: server.server_type.name,
host: server.public_net.ipv4.ip,
labels: server.labels
labels: server.labels,
};
}
private async createServer(
config: LeaseConfig,
leaseID: string,
owner: string
owner: string,
): Promise<HetznerServer> {
const name = `crabbox-${leaseID}`.replaceAll("_", "-");
const labels = {
@ -145,7 +148,7 @@ export class HetznerClient {
state: "leased",
keep: String(config.keep),
owner: sanitizeLabel(owner),
created_by: "crabbox"
created_by: "crabbox",
};
const response = await this.request<HetznerServerResponse>("POST", "/servers", {
name,
@ -158,8 +161,8 @@ export class HetznerClient {
start_after_create: true,
public_net: {
enable_ipv4: true,
enable_ipv6: false
}
enable_ipv6: false,
},
});
return response.server.public_net.ipv4.ip
? response.server
@ -171,15 +174,17 @@ export class HetznerClient {
method,
headers: {
authorization: `Bearer ${this.token}`,
"content-type": "application/json"
}
"content-type": "application/json",
},
};
if (body !== undefined) {
init.body = JSON.stringify(body);
}
const response = await fetch(`https://api.hetzner.cloud/v1${path}`, init);
if (!response.ok) {
throw new Error(`hetzner ${method} ${path}: http ${response.status}: ${await safeBody(response)}`);
throw new Error(
`hetzner ${method} ${path}: http ${response.status}: ${await safeBody(response)}`,
);
}
if (response.status === 204) {
return undefined as T;

View File

@ -7,7 +7,7 @@ export function json(data: unknown, init: ResponseInit = {}): Response {
export function text(message: string, status = 200): Response {
return new Response(message, {
status,
headers: { "content-type": "text/plain; charset=utf-8" }
headers: { "content-type": "text/plain; charset=utf-8" },
});
}

View File

@ -15,7 +15,7 @@ export default {
}
const id = env.FLEET.idFromName("default");
return env.FLEET.get(id).fetch(request);
}
},
};
export function isAuthorized(request: Request, env: Pick<Env, "CRABBOX_SHARED_TOKEN">): boolean {

View File

@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";
import { leaseConfig, serverTypeCandidatesForClass, serverTypeForClass } from "../src/config";
describe("machine class config", () => {
@ -9,7 +10,7 @@ describe("machine class config", () => {
"ccx53",
"ccx43",
"cpx62",
"cx53"
"cx53",
]);
});

View File

@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";
import { isAuthorized } from "../src";
describe("coordinator auth", () => {
@ -10,7 +11,7 @@ describe("coordinator auth", () => {
it("requires the configured bearer token", () => {
const denied = new Request("https://example.test/v1/pool");
const allowed = new Request("https://example.test/v1/pool", {
headers: { authorization: "Bearer secret" }
headers: { authorization: "Bearer secret" },
});
expect(isAuthorized(denied, { CRABBOX_SHARED_TOKEN: "secret" })).toBe(false);
expect(isAuthorized(allowed, { CRABBOX_SHARED_TOKEN: "secret" })).toBe(true);

View File

@ -3,6 +3,6 @@ import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "node",
globals: false
}
globals: false,
},
});

View File

@ -8,21 +8,21 @@
"routes": [
{
"pattern": "crabbox.clawd.bot/*",
"zone_name": "clawd.bot"
}
"zone_name": "clawd.bot",
},
],
"durable_objects": {
"bindings": [
{
"name": "FLEET",
"class_name": "FleetDurableObject"
}
]
"class_name": "FleetDurableObject",
},
],
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["FleetDurableObject"]
}
]
"new_sqlite_classes": ["FleetDurableObject"],
},
],
}