diff --git a/.env.example b/.env.example
index 464f28bb..0d64fcba 100644
--- a/.env.example
+++ b/.env.example
@@ -14,6 +14,6 @@ MANAGER_ASSET_REGEX=apk$
MANAGER_DOWNLOADERS_REPO=revanced-manager-downloaders
MANAGER_DOWNLOADERS_ASSET_REGEX=apk$
-CONTRIBUTORS_REPOS=revanced-patcher:ReVanced Patcher,revanced-patches:ReVanced Patches,revanced-website:ReVanced Website,revanced-cli:ReVanced CLI,revanced-manager:ReVanced Manager
+CONTRIBUTORS_REPOS=revanced-manager:ReVanced Manager,revanced-patches:ReVanced Patches,revanced-cli:ReVanced CLI,revanced-documentation:ReVanced Documentation,revanced-website:ReVanced Website,revanced-patcher:ReVanced Patcher,revanced-api:ReVanced API,revanced-manager-downloaders:ReVanced Manager Downloaders,revanced-bots:ReVanced Bots,revanced-library:ReVanced Library,revanced-patches-gradle-plugin:ReVanced Patches Gradle Plugin,revanced-patches-template:ReVanced Patches Template,revanced-cloudflare-email-worker:ReVanced Cloudflare Email Worker,revanced-encrypt:ReVanced Encrypt,revanced-vote:ReVanced Vote,revanced-invoice:ReVanced Invoice,revanced-manager-downloaders-template:ReVanced Manager Downloaders Template,revanced-branding:ReVanced Branding
API_VERSION=5
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 8bc7f7ee..6a7685b9 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -2,4 +2,4 @@ blank_issues_enabled: false
contact_links:
- name: 🗨 Discussions
url: https://github.com/revanced/revanced-suggestions/discussions
- about: Have something unspecific to ReVanced APi in mind? Search for or start a new discussion!
+ about: Have something unspecific to ReVanced API in mind? Search for or start a new discussion!
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index fab7f3fa..d2150183 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -13,7 +13,7 @@ body:
media="(prefers-color-scheme: dark)"
srcset="https://raw.githubusercontent.com/revanced/revanced-api/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
>
-
@@ -66,7 +66,7 @@ body:
Continuing the legacy of Vanced
- # ReVanced APi feature request
+ # ReVanced API feature request
Before creating a new feature request, please keep the following in mind:
@@ -82,10 +82,10 @@ body:
- type: textarea
attributes:
label: Motivation
- description: |
+ description: |
A strong motivation is necessary for a feature request to be considered.
-
- - Why should this feature be implemented?
+
+ - Why should this feature be implemented?
- What is the explicit use case?
- What are the benefits?
- What makes this feature important?
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..9b2d3386
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,5 @@
+.github/
+drizzle/
+bun.lock
+node_modules/
+*.md
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..867d92e9
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,5 @@
+{
+ "singleQuote": true,
+ "trailingComma": "none",
+ "tabWidth": 4
+}
diff --git a/about.example.json b/about.example.json
index 8bbaf4c9..cf59fbe6 100644
--- a/about.example.json
+++ b/about.example.json
@@ -1,85 +1,85 @@
{
- "name": "ReVanced",
- "about": "ReVanced was born out of Vanced's discontinuation and it is our goal to continue the legacy of what Vanced left behind. Thanks to ReVanced Patcher, it's possible to create long-lasting patches for nearly any Android app. ReVanced's patching system is designed to allow patches to work on new versions of the apps automatically with bare minimum maintenance.",
- "keys": "https://api.revanced.app/keys",
- "branding": {
- "logo": "https://raw.githubusercontent.com/ReVanced/revanced-branding/main/assets/revanced-logo/revanced-logo.svg"
- },
- "status": "https://status.revanced.app",
- "contact": {
- "email": "contact@revanced.app"
- },
- "socials": [
- {
- "name": "Website",
- "url": "https://revanced.app",
- "preferred": true
+ "name": "ReVanced",
+ "about": "ReVanced was born out of Vanced's discontinuation and it is our goal to continue the legacy of what Vanced left behind. Thanks to ReVanced Patcher, it's possible to create long-lasting patches for nearly any Android app. ReVanced's patching system is designed to allow patches to work on new versions of the apps automatically with bare minimum maintenance.",
+ "keys": "https://api.revanced.app/keys",
+ "branding": {
+ "logo": "https://raw.githubusercontent.com/ReVanced/revanced-branding/main/assets/revanced-logo/revanced-logo.svg"
},
- {
- "name": "GitHub",
- "url": "https://github.com/revanced"
+ "status": "https://status.revanced.app",
+ "contact": {
+ "email": "contact@revanced.app"
},
- {
- "name": "Twitter",
- "url": "https://twitter.com/revancedapp"
- },
- {
- "name": "Discord",
- "url": "https://revanced.app/discord",
- "preferred": true
- },
- {
- "name": "Reddit",
- "url": "https://www.reddit.com/r/revancedapp"
- },
- {
- "name": "Telegram",
- "url": "https://t.me/app_revanced"
- },
- {
- "name": "YouTube",
- "url": "https://www.youtube.com/@ReVanced"
- }
- ],
- "donations": {
- "wallets": [
- {
- "network": "Bitcoin",
- "currency_code": "BTC",
- "address": "bc1q4x8j6mt27y5gv0q625t8wkr87ruy8fprpy4v3f"
- },
- {
- "network": "Dogecoin",
- "currency_code": "DOGE",
- "address": "D8GH73rNjudgi6bS2krrXWEsU9KShedLXp",
- "preferred": true
- },
- {
- "network": "Ethereum",
- "currency_code": "ETH",
- "address": "0x7ab4091e00363654bf84B34151225742cd92FCE5"
- },
- {
- "network": "Litecoin",
- "currency_code": "LTC",
- "address": "LbJi8EuoDcwaZvykcKmcrM74jpjde23qJ2"
- },
- {
- "network": "Monero",
- "currency_code": "XMR",
- "address": "46YwWDbZD6jVptuk5mLHsuAmh1BnUMSjSNYacozQQEraWSQ93nb2yYVRHoMR6PmFYWEHsLHg9tr1cH5M8Rtn7YaaGQPCjSh"
- }
+ "socials": [
+ {
+ "name": "Website",
+ "url": "https://revanced.app",
+ "preferred": true
+ },
+ {
+ "name": "GitHub",
+ "url": "https://github.com/revanced"
+ },
+ {
+ "name": "Twitter",
+ "url": "https://twitter.com/revancedapp"
+ },
+ {
+ "name": "Discord",
+ "url": "https://revanced.app/discord",
+ "preferred": true
+ },
+ {
+ "name": "Reddit",
+ "url": "https://www.reddit.com/r/revancedapp"
+ },
+ {
+ "name": "Telegram",
+ "url": "https://t.me/app_revanced"
+ },
+ {
+ "name": "YouTube",
+ "url": "https://www.youtube.com/@ReVanced"
+ }
],
- "links": [
- {
- "name": "Open Collective",
- "url": "https://opencollective.com/revanced",
- "preferred": true
- },
- {
- "name": "GitHub Sponsors",
- "url": "https://github.com/sponsors/ReVanced"
- }
- ]
- }
-}
\ No newline at end of file
+ "donations": {
+ "wallets": [
+ {
+ "network": "Bitcoin",
+ "currency_code": "BTC",
+ "address": "bc1q4x8j6mt27y5gv0q625t8wkr87ruy8fprpy4v3f"
+ },
+ {
+ "network": "Dogecoin",
+ "currency_code": "DOGE",
+ "address": "D8GH73rNjudgi6bS2krrXWEsU9KShedLXp",
+ "preferred": true
+ },
+ {
+ "network": "Ethereum",
+ "currency_code": "ETH",
+ "address": "0x7ab4091e00363654bf84B34151225742cd92FCE5"
+ },
+ {
+ "network": "Litecoin",
+ "currency_code": "LTC",
+ "address": "LbJi8EuoDcwaZvykcKmcrM74jpjde23qJ2"
+ },
+ {
+ "network": "Monero",
+ "currency_code": "XMR",
+ "address": "46YwWDbZD6jVptuk5mLHsuAmh1BnUMSjSNYacozQQEraWSQ93nb2yYVRHoMR6PmFYWEHsLHg9tr1cH5M8Rtn7YaaGQPCjSh"
+ }
+ ],
+ "links": [
+ {
+ "name": "Open Collective",
+ "url": "https://opencollective.com/revanced",
+ "preferred": true
+ },
+ {
+ "name": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/ReVanced"
+ }
+ ]
+ }
+}
diff --git a/bun.lock b/bun.lock
index f1135e22..ba8ee245 100644
--- a/bun.lock
+++ b/bun.lock
@@ -5,23 +5,24 @@
"": {
"name": "revanced-api",
"dependencies": {
- "@hono/swagger-ui": "^0.6.0",
+ "@hono/swagger-ui": "^0.6.1",
"@hono/zod-openapi": "^1.2.2",
"drizzle-orm": "^0.45.1",
- "hono": "^4.12.5",
+ "hono": "^4.12.8",
"zod": "^4.3.6",
},
"devDependencies": {
- "@cloudflare/workers-types": "^4.20260307.1",
- "@kilianpaquier/semantic-release-backmerge": "^1.7.1",
+ "@cloudflare/workers-types": "^4.20260317.1",
+ "@kilianpaquier/semantic-release-backmerge": "^1.7.3",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@semantic-release/npm": "^13.1.5",
- "@types/node": "^25.3.5",
- "drizzle-kit": "^0.31.9",
+ "@types/node": "^25.5.0",
+ "drizzle-kit": "^0.31.10",
+ "prettier": "3.8.1",
"semantic-release": "^25.0.3",
"typescript": "^5.9.3",
- "wrangler": "^4.71.0",
+ "wrangler": "^4.76.0",
},
},
},
@@ -42,19 +43,19 @@
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="],
- "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.15.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-EGYmJaGZKWl+X8tXxcnx4v2bOZSjQeNI5dWFeXivgX9+YCT69AkzHHwlNbVpqtEUTbew8eQurpyOpeN8fg00nw=="],
+ "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg=="],
- "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260312.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-HUAtDWaqUduS6yasV6+NgsK7qBpP1qGU49ow/Wb117IHjYp+PZPUGReDYocpB4GOMRoQlvdd4L487iFxzdARpw=="],
+ "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260317.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-8hjh3sPMwY8M/zedq3/sXoA2Q4BedlGufn3KOOleIG+5a4ReQKLlUah140D7J6zlKmYZAFMJ4tWC7hCuI/s79g=="],
- "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260312.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DOn7TPTHSxJYfi4m4NYga/j32wOTqvJf/pY4Txz5SDKWIZHSTXFyGz2K4B+thoPWLop/KZxGoyTv7db0mk/qyw=="],
+ "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260317.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-M/MnNyvO5HMgoIdr3QHjdCj2T1ki9gt0vIUnxYxBu9ISXS/jgtMl6chUVPJ7zHYBn9MyYr8ByeN6frjYxj0MGg=="],
- "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260312.1", "", { "os": "linux", "cpu": "x64" }, "sha512-TdkIh3WzPXYHuvz7phAtFEEvAxvFd30tHrm4gsgpw0R0F5b8PtoM3hfL2uY7EcBBWVYUBtkY2ahDYFfufnXw/g=="],
+ "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260317.1", "", { "os": "linux", "cpu": "x64" }, "sha512-1ltuEjkRcS3fsVF7CxsKlWiRmzq2ZqMfqDN0qUOgbUwkpXsLVJsXmoblaLf5OP00ELlcgF0QsN0p2xPEua4Uug=="],
- "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260312.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-kNauZhL569Iy94t844OMwa1zP6zKFiL3xiJ4tGLS+TFTEfZ3pZsRH6lWWOtkXkjTyCmBEOog0HSEKjIV4oAffw=="],
+ "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260317.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-3QrNnPF1xlaNwkHpasvRvAMidOvQs2NhXQmALJrEfpIJ/IDL2la8g499yXp3eqhG3hVMCB07XVY149GTs42Xtw=="],
- "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260312.1", "", { "os": "win32", "cpu": "x64" }, "sha512-5dBrlSK+nMsZy5bYQpj8t9iiQNvCRlkm9GGvswJa9vVU/1BNO4BhJMlqOLWT24EmFyApZ+kaBiPJMV8847NDTg=="],
+ "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260317.1", "", { "os": "win32", "cpu": "x64" }, "sha512-MfZTz+7LfuIpMGTa3RLXHX8Z/pnycZLItn94WRdHr8LPVet+C5/1Nzei399w/jr3+kzT4pDKk26JF/tlI5elpQ=="],
- "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260313.1", "", {}, "sha512-jMEeX3RKfOSVqqXRKr/ulgglcTloeMzSH3FdzIfqJHtvc12/ELKd5Ldsg8ZHahKX/4eRxYdw3kbzb8jLXbq/jQ=="],
+ "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260317.1", "", {}, "sha512-+G4eVwyCpm8Au1ex8vQBCuA9wnwqetz4tPNRoB/53qvktERWBRMQnrtvC1k584yRE3emMThtuY0gWshvSJ++PQ=="],
"@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="],
@@ -182,7 +183,7 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
- "@kilianpaquier/semantic-release-backmerge": ["@kilianpaquier/semantic-release-backmerge@1.7.1", "", { "dependencies": { "@octokit/core": "7.0.6", "@semantic-release/error": "4.0.0", "aggregate-error": "5.0.0", "debug": "4.4.3", "execa": "9.6.1", "git-up": "8.1.1", "git-url-parse": "16.1.0", "lodash": "4.17.23", "node-fetch": "3.3.2", "semantic-release": "25.0.2", "semver": "7.7.3", "url-join": "5.0.0" } }, "sha512-R2ARmmzpcfkBm2SMMqdOkvb7a3uRtrHyXPUzZYHEBVjX5sQrv5d57sWs9NhFef2aTbL8bXqFredqSA+MqD7pxg=="],
+ "@kilianpaquier/semantic-release-backmerge": ["@kilianpaquier/semantic-release-backmerge@1.7.3", "", { "dependencies": { "@octokit/core": "7.0.6", "@semantic-release/error": "4.0.0", "aggregate-error": "5.0.0", "debug": "4.4.3", "execa": "9.6.1", "git-up": "8.1.1", "git-url-parse": "16.1.0", "lodash": "4.17.23", "node-fetch": "3.3.2", "semantic-release": "25.0.3", "semver": "7.7.4", "url-join": "5.0.0" } }, "sha512-H18O9omYVo925KF2sanfi5TnQ5Fh1jtsPIX7s+lC9CfWlWXJ4nywvCOY6ax+hYFZR/AFTG9zgHwD6AZL4HambA=="],
"@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="],
@@ -330,7 +331,7 @@
"dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="],
- "drizzle-kit": ["drizzle-kit@0.31.9", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg=="],
+ "drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="],
"drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="],
@@ -352,8 +353,6 @@
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
- "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
-
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
@@ -512,7 +511,7 @@
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
- "miniflare": ["miniflare@4.20260312.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.18.2", "workerd": "1.20260312.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-pieP2rfXynPT6VRINYaiHe/tfMJ4c5OIhqRlIdLF6iZ9g5xgpEmvimvIgMpgAdDJuFlrLcwDUi8MfAo2R6dt/w=="],
+ "miniflare": ["miniflare@4.20260317.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.4", "workerd": "1.20260317.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-A3csI1HXEIfqe3oscgpoRMHdYlkReQKPH/g5JE53vFSjoM6YIAOGAzyDNeYffwd9oQkPWDj9xER8+vpxei8klA=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
@@ -596,6 +595,8 @@
"pkg-conf": ["pkg-conf@2.1.0", "", { "dependencies": { "find-up": "^2.0.0", "load-json-file": "^4.0.0" } }, "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g=="],
+ "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
+
"pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="],
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
@@ -624,9 +625,7 @@
"semantic-release": ["semantic-release@25.0.3", "", { "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", "@semantic-release/github": "^12.0.0", "@semantic-release/npm": "^13.1.1", "@semantic-release/release-notes-generator": "^14.1.0", "aggregate-error": "^5.0.0", "cosmiconfig": "^9.0.0", "debug": "^4.0.0", "env-ci": "^11.0.0", "execa": "^9.0.0", "figures": "^6.0.0", "find-versions": "^6.0.0", "get-stream": "^6.0.0", "git-log-parser": "^1.2.0", "hook-std": "^4.0.0", "hosted-git-info": "^9.0.0", "import-from-esm": "^2.0.0", "lodash-es": "^4.17.21", "marked": "^15.0.0", "marked-terminal": "^7.3.0", "micromatch": "^4.0.2", "p-each-series": "^3.0.0", "p-reduce": "^3.0.0", "read-package-up": "^12.0.0", "resolve-from": "^5.0.0", "semver": "^7.3.2", "signale": "^1.2.1", "yargs": "^18.0.0" }, "bin": "bin/semantic-release.js" }, "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA=="],
- "semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
-
- "semver-diff": ["semver-diff@5.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-0HbGtOm+S7T6NGQ/pxJSJipJvc4DK3FcRVMRkhsIwJDJ4Jcz5DQC1cPPzB5GhzyHjwttW878HaWQq46CkL3cqg=="],
+ "semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"semver-regex": ["semver-regex@4.0.5", "", {}, "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw=="],
@@ -700,6 +699,8 @@
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+ "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
+
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
"type-fest": ["type-fest@5.4.4", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw=="],
@@ -738,9 +739,9 @@
"wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
- "workerd": ["workerd@1.20260312.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260312.1", "@cloudflare/workerd-darwin-arm64": "1.20260312.1", "@cloudflare/workerd-linux-64": "1.20260312.1", "@cloudflare/workerd-linux-arm64": "1.20260312.1", "@cloudflare/workerd-windows-64": "1.20260312.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-nNpPkw9jaqo79B+iBCOiksx+N62xC+ETIfyzofUEdY3cSOHJg6oNnVSHm7vHevzVblfV76c8Gr0cXHEapYMBEg=="],
+ "workerd": ["workerd@1.20260317.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260317.1", "@cloudflare/workerd-darwin-arm64": "1.20260317.1", "@cloudflare/workerd-linux-64": "1.20260317.1", "@cloudflare/workerd-linux-arm64": "1.20260317.1", "@cloudflare/workerd-windows-64": "1.20260317.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-ZuEq1OdrJBS+NV+L5HMYPCzVn49a2O60slQiiLpG44jqtlOo+S167fWC76kEXteXLLLydeuRrluRel7WdOUa4g=="],
- "wrangler": ["wrangler@4.73.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.15.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260312.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260312.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260312.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-VJXsqKDFCp6OtFEHXITSOR5kh95JOknwPY8m7RyQuWJQguSybJy43m4vhoCSt42prutTef7eeuw7L4V4xiynGw=="],
+ "wrangler": ["wrangler@4.76.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.16.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260317.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260317.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260317.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-Wan+CU5a0tu4HIxGOrzjNbkmxCT27HUmzrMj6kc7aoAnjSLv50Ggcn2Ant7wNQrD6xW3g31phKupZJgTZ8wZfQ=="],
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
@@ -768,8 +769,6 @@
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": "bin/esbuild" }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
- "@kilianpaquier/semantic-release-backmerge/semantic-release": ["semantic-release@25.0.2", "", { "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", "@semantic-release/github": "^12.0.0", "@semantic-release/npm": "^13.1.1", "@semantic-release/release-notes-generator": "^14.1.0", "aggregate-error": "^5.0.0", "cosmiconfig": "^9.0.0", "debug": "^4.0.0", "env-ci": "^11.0.0", "execa": "^9.0.0", "figures": "^6.0.0", "find-versions": "^6.0.0", "get-stream": "^6.0.0", "git-log-parser": "^1.2.0", "hook-std": "^4.0.0", "hosted-git-info": "^9.0.0", "import-from-esm": "^2.0.0", "lodash-es": "^4.17.21", "marked": "^15.0.0", "marked-terminal": "^7.3.0", "micromatch": "^4.0.2", "p-each-series": "^3.0.0", "p-reduce": "^3.0.0", "read-package-up": "^12.0.0", "resolve-from": "^5.0.0", "semver": "^7.3.2", "semver-diff": "^5.0.0", "signale": "^1.2.1", "yargs": "^18.0.0" }, "bin": "bin/semantic-release.js" }, "sha512-6qGjWccl5yoyugHt3jTgztJ9Y0JVzyH8/Voc/D8PlLat9pwxQYXz7W1Dpnq5h0/G5GCYGUaDSlYcyk3AMh5A6g=="],
-
"@pnpm/network.ca-file/graceful-fs": ["graceful-fs@4.2.10", "", {}, "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="],
"@poppinss/dumper/@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
@@ -786,8 +785,6 @@
"@semantic-release/git/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
- "@semantic-release/npm/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
-
"@semantic-release/release-notes-generator/get-stream": ["get-stream@7.0.1", "", {}, "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ=="],
"@semantic-release/release-notes-generator/read-package-up": ["read-package-up@11.0.0", "", { "dependencies": { "find-up-simple": "^1.0.0", "read-pkg": "^9.0.0", "type-fest": "^4.6.0" } }, "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ=="],
@@ -798,8 +795,6 @@
"cli-table3/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
- "conventional-changelog-writer/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
-
"cosmiconfig/parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
"crypto-random-string/type-fest": ["type-fest@1.4.0", "", {}, "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA=="],
@@ -816,9 +811,7 @@
"make-asynchronous/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
- "miniflare/undici": ["undici@7.18.2", "", {}, "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw=="],
-
- "normalize-package-data/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+ "miniflare/undici": ["undici@7.24.4", "", {}, "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w=="],
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
@@ -830,12 +823,6 @@
"semantic-release/p-reduce": ["p-reduce@3.0.0", "", {}, "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q=="],
- "semantic-release/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
-
- "semver-diff/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
-
- "sharp/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
-
"signale/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
"signale/figures": ["figures@2.0.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA=="],
@@ -846,6 +833,8 @@
"tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
+ "tsx/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
+
"wrangler/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
@@ -894,8 +883,6 @@
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
- "@kilianpaquier/semantic-release-backmerge/semantic-release/p-reduce": ["p-reduce@3.0.0", "", {}, "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q=="],
-
"@semantic-release/changelog/aggregate-error/clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="],
"@semantic-release/changelog/aggregate-error/indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
@@ -948,6 +935,58 @@
"signale/figures/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
+ "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
+
+ "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
+
+ "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
+
+ "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
+
+ "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
+
+ "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
+
+ "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
+
+ "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
+
+ "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
+
+ "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
+
+ "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
+
+ "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
+
+ "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
+
+ "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
+
+ "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
+
+ "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
+
+ "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
+
+ "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
+
+ "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
+
+ "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
+
+ "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
+
+ "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
+
+ "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
+
+ "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
+
+ "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
+
+ "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
+
"wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
"wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
@@ -1024,8 +1063,6 @@
"@semantic-release/release-notes-generator/read-package-up/read-pkg/normalize-package-data/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="],
- "@semantic-release/release-notes-generator/read-package-up/read-pkg/normalize-package-data/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
-
"cli-highlight/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"cli-highlight/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
diff --git a/drizzle.config.ts b/drizzle.config.ts
index 31edb0e9..0d9f68b3 100644
--- a/drizzle.config.ts
+++ b/drizzle.config.ts
@@ -1,7 +1,7 @@
-import { defineConfig } from "drizzle-kit";
+import { defineConfig } from 'drizzle-kit';
export default defineConfig({
- out: "./drizzle/migrations",
- schema: "./src/db/schema.ts",
- dialect: "sqlite",
+ out: './drizzle/migrations',
+ schema: './src/db/schema.ts',
+ dialect: 'sqlite'
});
diff --git a/package.json b/package.json
index 442308a9..a5e7f76a 100644
--- a/package.json
+++ b/package.json
@@ -1,39 +1,41 @@
{
- "name": "revanced-api",
- "version": "1.8.0",
- "description": "API server for ReVanced.",
- "type": "module",
- "scripts": {
- "dev": "wrangler dev",
- "deploy": "wrangler deploy",
- "db:create": "wrangler d1 create revanced-api",
- "db:migration:generate": "drizzle-kit generate",
- "db:migration:apply": "wrangler d1 migrations apply revanced-api"
- },
- "keywords": [
- "revanced",
- "api",
- "hono",
- "cloudflare-workers"
- ],
- "license": "AGPL-3.0",
- "dependencies": {
- "@hono/swagger-ui": "^0.6.1",
- "@hono/zod-openapi": "^1.2.2",
- "drizzle-orm": "^0.45.1",
- "hono": "^4.12.8",
- "zod": "^4.3.6"
- },
- "devDependencies": {
- "@cloudflare/workers-types": "^4.20260313.1",
- "@kilianpaquier/semantic-release-backmerge": "^1.7.1",
- "@semantic-release/changelog": "^6.0.3",
- "@semantic-release/git": "^10.0.1",
- "@semantic-release/npm": "^13.1.5",
- "@types/node": "^25.5.0",
- "drizzle-kit": "^0.31.9",
- "semantic-release": "^25.0.3",
- "typescript": "^5.9.3",
- "wrangler": "^4.73.0"
- }
+ "name": "revanced-api",
+ "version": "1.8.0",
+ "description": "API server for ReVanced.",
+ "type": "module",
+ "scripts": {
+ "dev": "wrangler dev",
+ "deploy": "wrangler deploy",
+ "format": "prettier --write --ignore-unknown .",
+ "db:create": "wrangler d1 create revanced-api",
+ "db:migration:generate": "drizzle-kit generate",
+ "db:migration:apply": "wrangler d1 migrations apply revanced-api"
+ },
+ "keywords": [
+ "revanced",
+ "api",
+ "hono",
+ "cloudflare-workers"
+ ],
+ "license": "AGPL-3.0",
+ "dependencies": {
+ "@hono/swagger-ui": "^0.6.1",
+ "@hono/zod-openapi": "^1.2.2",
+ "drizzle-orm": "^0.45.1",
+ "hono": "^4.12.8",
+ "zod": "^4.3.6"
+ },
+ "devDependencies": {
+ "@cloudflare/workers-types": "^4.20260317.1",
+ "@kilianpaquier/semantic-release-backmerge": "^1.7.3",
+ "@semantic-release/changelog": "^6.0.3",
+ "@semantic-release/git": "^10.0.1",
+ "@semantic-release/npm": "^13.1.5",
+ "@types/node": "^25.5.0",
+ "drizzle-kit": "^0.31.10",
+ "prettier": "3.8.1",
+ "semantic-release": "^25.0.3",
+ "typescript": "^5.9.3",
+ "wrangler": "^4.76.0"
+ }
}
diff --git a/src/auth/auth.ts b/src/auth/auth.ts
index 6e05baa2..73a89e4d 100644
--- a/src/auth/auth.ts
+++ b/src/auth/auth.ts
@@ -1,23 +1,31 @@
-import type { MiddlewareHandler } from "hono";
-import type { Env } from "../types";
+import type { MiddlewareHandler } from 'hono';
+import type { Env } from '../types';
// Checks the Authorization header against the API_TOKEN env var.
-export const authMiddleware: MiddlewareHandler<{ Bindings: Env }> = async (c, next) => {
- const authHeader = c.req.header("Authorization");
+export const authMiddleware: MiddlewareHandler<{ Bindings: Env }> = async (
+ c,
+ next
+) => {
+ const authHeader = c.req.header('Authorization');
- if (!authHeader) {
- return c.json({ error: "Missing Authorization header" }, 401);
- }
+ if (!authHeader) {
+ return c.json({ error: 'Missing Authorization header' }, 401);
+ }
- const [scheme, token] = authHeader.split(" ", 2);
+ const [scheme, token] = authHeader.split(' ', 2);
- if (scheme !== "Bearer" || !token) {
- return c.json({ error: "Invalid Authorization header format. Expected: Bearer " }, 401);
- }
+ if (scheme !== 'Bearer' || !token) {
+ return c.json(
+ {
+ error: 'Invalid Authorization header format. Expected: Bearer '
+ },
+ 401
+ );
+ }
- if (token !== c.env.API_TOKEN) {
- return c.json({ error: "Invalid token" }, 403);
- }
+ if (token !== c.env.API_TOKEN) {
+ return c.json({ error: 'Invalid token' }, 403);
+ }
- await next();
+ await next();
};
diff --git a/src/backend/github.ts b/src/backend/github.ts
index bb7ca1d0..747d0cf4 100644
--- a/src/backend/github.ts
+++ b/src/backend/github.ts
@@ -1,170 +1,187 @@
import type {
- Backend,
- BackendRelease,
- BackendAsset,
- BackendContributor,
- BackendMember,
-} from "./types";
+ Backend,
+ BackendRelease,
+ BackendAsset,
+ BackendContributor,
+ BackendMember
+} from './types';
interface GitHubAsset {
- name: string;
- browser_download_url: string;
+ name: string;
+ browser_download_url: string;
}
interface GitHubRelease {
- tag_name: string;
- body: string;
- created_at: string;
- prerelease: boolean;
- assets: GitHubAsset[];
+ tag_name: string;
+ body: string;
+ created_at: string;
+ prerelease: boolean;
+ assets: GitHubAsset[];
}
interface GitHubContributor {
- login: string;
- avatar_url: string;
- html_url: string;
- contributions: number;
+ login: string;
+ avatar_url: string;
+ html_url: string;
+ contributions: number;
}
interface GitHubMember {
- login: string;
- avatar_url: string;
- html_url: string;
+ login: string;
+ avatar_url: string;
+ html_url: string;
}
interface GitHubUser {
- login: string;
- avatar_url: string;
- html_url: string;
- bio: string | null;
+ login: string;
+ avatar_url: string;
+ html_url: string;
+ bio: string | null;
}
interface GitHubGpgKey {
- key_id: string;
+ key_id: string;
}
function formatDatetime(isoString: string): string {
- return isoString
- .replace(/\.\d{3}Z$/, "")
- .replace(/Z$/, "")
- .replace(/[+-]\d{2}:\d{2}$/, "");
+ return isoString
+ .replace(/\.\d{3}Z$/, '')
+ .replace(/Z$/, '')
+ .replace(/[+-]\d{2}:\d{2}$/, '');
}
export class GitHubBackend implements Backend {
- private readonly baseUrl = "https://api.github.com";
- private readonly headers: HeadersInit;
-
- constructor(token?: string) {
- const headers: Record = {
- Accept: "application/vnd.github+json",
- "User-Agent": "revanced-api",
- };
- if (token) {
- headers["Authorization"] = `Bearer ${token}`;
+ private readonly baseUrl = 'https://api.github.com';
+ private readonly headers: HeadersInit;
+
+ constructor(token?: string) {
+ const headers: Record = {
+ Accept: 'application/vnd.github+json',
+ 'User-Agent': 'revanced-api'
+ };
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+ this.headers = headers;
}
- this.headers = headers;
- }
- private async fetchJson(url: string): Promise {
- const response = await fetch(url, { headers: this.headers });
- if (!response.ok) {
- throw new Error(`GitHub API error: ${response.status} ${response.statusText} — ${url}`);
- }
- return response.json() as Promise;
- }
-
- async release(owner: string, repo: string, prerelease: boolean): Promise {
- let release: GitHubRelease;
-
- if (prerelease) {
- const releases = await this.fetchJson(
- `${this.baseUrl}/repos/${owner}/${repo}/releases?per_page=1`,
- );
- if (releases.length === 0) {
- throw new Error(`No releases found for ${owner}/${repo}`);
- }
- release = releases[0];
- } else {
- release = await this.fetchJson(
- `${this.baseUrl}/repos/${owner}/${repo}/releases/latest`,
- );
+ private async fetchJson(url: string): Promise {
+ const response = await fetch(url, { headers: this.headers });
+ if (!response.ok) {
+ throw new Error(
+ `GitHub API error: ${response.status} ${response.statusText} — ${url}`
+ );
+ }
+ return response.json() as Promise;
}
- return {
- tag: release.tag_name,
- releaseNote: release.body ?? "",
- createdAt: formatDatetime(release.created_at),
- prerelease: release.prerelease,
- assets: release.assets.map(
- (asset): BackendAsset => ({
- name: asset.name,
- downloadUrl: asset.browser_download_url,
- }),
- ),
- };
- }
-
- async releases(owner: string, repo: string, count: number): Promise {
- const releases = await this.fetchJson(
- `${this.baseUrl}/repos/${owner}/${repo}/releases?per_page=${count}`,
- );
-
- return releases.map((release) => ({
- tag: release.tag_name,
- releaseNote: release.body ?? "",
- createdAt: formatDatetime(release.created_at),
- prerelease: release.prerelease,
- assets: release.assets.map(
- (asset): BackendAsset => ({
- name: asset.name,
- downloadUrl: asset.browser_download_url,
- }),
- ),
- }));
- }
-
- async contributors(owner: string, repo: string): Promise {
- const contributors = await this.fetchJson(
- `${this.baseUrl}/repos/${owner}/${repo}/contributors?per_page=100`,
- );
-
- return contributors.map((contributor) => ({
- name: contributor.login,
- avatarUrl: contributor.avatar_url,
- url: contributor.html_url,
- contributions: contributor.contributions,
- }));
- }
-
- async members(organization: string): Promise {
- const publicMembers = await this.fetchJson(
- `${this.baseUrl}/orgs/${organization}/public_members`,
- );
-
- const members = await Promise.all(
- publicMembers.map(async (member) => {
- const [user, gpgKeys] = await Promise.all([
- this.fetchJson(`${this.baseUrl}/users/${member.login}`),
- this.fetchJson(`${this.baseUrl}/users/${member.login}/gpg_keys`),
- ]);
+ async release(
+ owner: string,
+ repo: string,
+ prerelease: boolean
+ ): Promise {
+ let release: GitHubRelease;
+
+ if (prerelease) {
+ const releases = await this.fetchJson(
+ `${this.baseUrl}/repos/${owner}/${repo}/releases?per_page=1`
+ );
+ if (releases.length === 0) {
+ throw new Error(`No releases found for ${owner}/${repo}`);
+ }
+ release = releases[0];
+ } else {
+ release = await this.fetchJson(
+ `${this.baseUrl}/repos/${owner}/${repo}/releases/latest`
+ );
+ }
return {
- name: user.login,
- avatarUrl: user.avatar_url,
- url: user.html_url,
- bio: user.bio,
- gpgKeys: {
- ids: gpgKeys.map((key) => key.key_id),
- url: `https://github.com/${user.login}.gpg`,
- },
- } satisfies BackendMember;
- }),
- );
-
- return members;
- }
-
- repositoryUrl(owner: string, repo: string): string {
- return `https://github.com/${owner}/${repo}`;
- }
+ tag: release.tag_name,
+ releaseNote: release.body ?? '',
+ createdAt: formatDatetime(release.created_at),
+ prerelease: release.prerelease,
+ assets: release.assets.map(
+ (asset): BackendAsset => ({
+ name: asset.name,
+ downloadUrl: asset.browser_download_url
+ })
+ )
+ };
+ }
+
+ async releases(
+ owner: string,
+ repo: string,
+ count: number
+ ): Promise {
+ const releases = await this.fetchJson(
+ `${this.baseUrl}/repos/${owner}/${repo}/releases?per_page=${count}`
+ );
+
+ return releases.map((release) => ({
+ tag: release.tag_name,
+ releaseNote: release.body ?? '',
+ createdAt: formatDatetime(release.created_at),
+ prerelease: release.prerelease,
+ assets: release.assets.map(
+ (asset): BackendAsset => ({
+ name: asset.name,
+ downloadUrl: asset.browser_download_url
+ })
+ )
+ }));
+ }
+
+ async contributors(
+ owner: string,
+ repo: string
+ ): Promise {
+ const contributors = await this.fetchJson(
+ `${this.baseUrl}/repos/${owner}/${repo}/contributors?per_page=100`
+ );
+
+ return contributors.map((contributor) => ({
+ name: contributor.login,
+ avatarUrl: contributor.avatar_url,
+ url: contributor.html_url,
+ contributions: contributor.contributions
+ }));
+ }
+
+ async members(organization: string): Promise {
+ const publicMembers = await this.fetchJson(
+ `${this.baseUrl}/orgs/${organization}/public_members`
+ );
+
+ const members = await Promise.all(
+ publicMembers.map(async (member) => {
+ const [user, gpgKeys] = await Promise.all([
+ this.fetchJson(
+ `${this.baseUrl}/users/${member.login}`
+ ),
+ this.fetchJson(
+ `${this.baseUrl}/users/${member.login}/gpg_keys`
+ )
+ ]);
+
+ return {
+ name: user.login,
+ avatarUrl: user.avatar_url,
+ url: user.html_url,
+ bio: user.bio,
+ gpgKeys: {
+ ids: gpgKeys.map((key) => key.key_id),
+ url: `https://github.com/${user.login}.gpg`
+ }
+ } satisfies BackendMember;
+ })
+ );
+
+ return members;
+ }
+
+ repositoryUrl(owner: string, repo: string): string {
+ return `https://github.com/${owner}/${repo}`;
+ }
}
diff --git a/src/backend/types.ts b/src/backend/types.ts
index 11d426eb..59487e5e 100644
--- a/src/backend/types.ts
+++ b/src/backend/types.ts
@@ -2,40 +2,48 @@
// Implement this to swap GitHub for GitLab, Gitea, or any other provider.
export interface BackendRelease {
- tag: string;
- releaseNote: string;
- createdAt: string; // ISO 8601 datetime without timezone suffix.
- prerelease: boolean;
- assets: BackendAsset[];
+ tag: string;
+ releaseNote: string;
+ createdAt: string; // ISO 8601 datetime without timezone suffix.
+ prerelease: boolean;
+ assets: BackendAsset[];
}
export interface BackendAsset {
- name: string;
- downloadUrl: string;
+ name: string;
+ downloadUrl: string;
}
export interface BackendContributor {
- name: string;
- avatarUrl: string;
- url: string;
- contributions: number;
+ name: string;
+ avatarUrl: string;
+ url: string;
+ contributions: number;
}
export interface BackendMember {
- name: string;
- avatarUrl: string;
- url: string;
- bio: string | null;
- gpgKeys: {
- ids: string[];
+ name: string;
+ avatarUrl: string;
url: string;
- };
+ bio: string | null;
+ gpgKeys: {
+ ids: string[];
+ url: string;
+ };
}
export interface Backend {
- release(owner: string, repo: string, prerelease: boolean): Promise;
- releases(owner: string, repo: string, count: number): Promise;
- contributors(owner: string, repo: string): Promise;
- members(organization: string): Promise;
- repositoryUrl(owner: string, repo: string): string;
+ release(
+ owner: string,
+ repo: string,
+ prerelease: boolean
+ ): Promise;
+ releases(
+ owner: string,
+ repo: string,
+ count: number
+ ): Promise;
+ contributors(owner: string, repo: string): Promise;
+ members(organization: string): Promise;
+ repositoryUrl(owner: string, repo: string): string;
}
diff --git a/src/cache.ts b/src/cache.ts
index 171aa4cc..40eb880c 100644
--- a/src/cache.ts
+++ b/src/cache.ts
@@ -1,15 +1,15 @@
-import type { MiddlewareHandler } from "hono";
+import type { MiddlewareHandler } from 'hono';
const SECONDS_PER_DAY = 86400;
/** Cache duration presets matching the original Kotlin API. */
export const CacheDuration = {
- /** 5 minutes — default for most API routes. */
- short: 5 * 60,
- /** 1 day — for contributors, team, about. */
- day: SECONDS_PER_DAY,
- /** 356 days — for public keys (essentially immutable). */
- immutable: 356 * SECONDS_PER_DAY,
+ /** 5 minutes — default for most API routes. */
+ short: 5 * 60,
+ /** 1 day — for contributors, team, about. */
+ day: SECONDS_PER_DAY,
+ /** 356 days — for public keys (essentially immutable). */
+ immutable: 356 * SECONDS_PER_DAY
} as const;
/**
@@ -17,9 +17,9 @@ export const CacheDuration = {
* Cloudflare's CDN will respect `max-age` for edge caching.
*/
export function cacheControl(maxAgeSeconds: number): MiddlewareHandler {
- const value = `public, max-age=${maxAgeSeconds}`;
- return async (c, next) => {
- c.header("Cache-Control", value);
- await next();
- };
+ const value = `public, max-age=${maxAgeSeconds}`;
+ return async (c, next) => {
+ c.header('Cache-Control', value);
+ await next();
+ };
}
diff --git a/src/config.ts b/src/config.ts
index d4e41c9d..66af8ce8 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1,51 +1,53 @@
-import type { Env } from "./types";
-import { GitHubBackend } from "./backend/github";
+import type { Env } from './types';
+import { GitHubBackend } from './backend/github';
export interface Config {
- organization: string;
- patches: {
- repo: string;
- assetRegex: RegExp;
- signatureAssetRegex: RegExp;
- publicKeyFile: string;
- };
- manager: {
- repo: string;
- assetRegex: RegExp;
- downloadersRepo: string;
- downloadersAssetRegex: RegExp;
- };
- contributorRepos: { repo: string; name: string }[];
- apiVersion: string;
+ organization: string;
+ patches: {
+ repo: string;
+ assetRegex: RegExp;
+ signatureAssetRegex: RegExp;
+ publicKeyFile: string;
+ };
+ manager: {
+ repo: string;
+ assetRegex: RegExp;
+ downloadersRepo: string;
+ downloadersAssetRegex: RegExp;
+ };
+ contributorRepos: { repo: string; name: string }[];
+ apiVersion: string;
}
let _config: Config | undefined;
export function getConfig(env: Env): Config {
- return (_config ??= {
- organization: env.ORGANIZATION,
- patches: {
- repo: env.PATCHES_REPO,
- assetRegex: new RegExp(env.PATCHES_ASSET_REGEX),
- signatureAssetRegex: new RegExp(env.PATCHES_SIGNATURE_ASSET_REGEX),
- publicKeyFile: env.PATCHES_PUBLIC_KEY_FILE,
- },
- manager: {
- repo: env.MANAGER_REPO,
- assetRegex: new RegExp(env.MANAGER_ASSET_REGEX),
- downloadersRepo: env.MANAGER_DOWNLOADERS_REPO,
- downloadersAssetRegex: new RegExp(env.MANAGER_DOWNLOADERS_ASSET_REGEX),
- },
- contributorRepos: env.CONTRIBUTORS_REPOS.split(",").map((entry) => {
- const [repo, ...nameParts] = entry.trim().split(":");
- return { repo: repo.trim(), name: nameParts.join(":").trim() };
- }),
- apiVersion: env.API_VERSION,
- });
+ return (_config ??= {
+ organization: env.ORGANIZATION,
+ patches: {
+ repo: env.PATCHES_REPO,
+ assetRegex: new RegExp(env.PATCHES_ASSET_REGEX),
+ signatureAssetRegex: new RegExp(env.PATCHES_SIGNATURE_ASSET_REGEX),
+ publicKeyFile: env.PATCHES_PUBLIC_KEY_FILE
+ },
+ manager: {
+ repo: env.MANAGER_REPO,
+ assetRegex: new RegExp(env.MANAGER_ASSET_REGEX),
+ downloadersRepo: env.MANAGER_DOWNLOADERS_REPO,
+ downloadersAssetRegex: new RegExp(
+ env.MANAGER_DOWNLOADERS_ASSET_REGEX
+ )
+ },
+ contributorRepos: env.CONTRIBUTORS_REPOS.split(',').map((entry) => {
+ const [repo, ...nameParts] = entry.trim().split(':');
+ return { repo: repo.trim(), name: nameParts.join(':').trim() };
+ }),
+ apiVersion: env.API_VERSION
+ });
}
let _backend: GitHubBackend | undefined;
export function getBackend(env: Env): GitHubBackend {
- return (_backend ??= new GitHubBackend(env.GITHUB_TOKEN));
+ return (_backend ??= new GitHubBackend(env.GITHUB_TOKEN));
}
diff --git a/src/db/client.ts b/src/db/client.ts
index bd265791..e6e810c2 100644
--- a/src/db/client.ts
+++ b/src/db/client.ts
@@ -1,9 +1,9 @@
-import { drizzle } from "drizzle-orm/d1";
-import * as schema from "./schema";
-import type { Database } from "../types";
+import { drizzle } from 'drizzle-orm/d1';
+import * as schema from './schema';
+import type { Database } from '../types';
let _database: Database | undefined;
export function getDatabase(d1: D1Database): Database {
- return (_database ??= drizzle(d1, { schema }));
+ return (_database ??= drizzle(d1, { schema }));
}
diff --git a/src/db/schema.ts b/src/db/schema.ts
index ddeffa5c..ce87de57 100644
--- a/src/db/schema.ts
+++ b/src/db/schema.ts
@@ -1,40 +1,40 @@
import {
- index,
- integer,
- primaryKey,
- sqliteTable,
- text,
-} from "drizzle-orm/sqlite-core";
+ index,
+ integer,
+ primaryKey,
+ sqliteTable,
+ text
+} from 'drizzle-orm/sqlite-core';
-export const announcements = sqliteTable("announcements", {
- id: integer("id").primaryKey({ autoIncrement: true }),
- author: text("author"),
- title: text("title").notNull(),
- content: text("content"),
- createdAt: text("created_at")
- .notNull()
- .$defaultFn(() => new Date().toISOString()),
- archivedAt: text("archived_at"),
- level: integer("level").notNull().default(0),
+export const announcements = sqliteTable('announcements', {
+ id: integer('id').primaryKey({ autoIncrement: true }),
+ author: text('author'),
+ title: text('title').notNull(),
+ content: text('content'),
+ createdAt: text('created_at')
+ .notNull()
+ .$defaultFn(() => new Date().toISOString()),
+ archivedAt: text('archived_at'),
+ level: integer('level').notNull().default(0)
});
-export const tags = sqliteTable("tags", {
- id: integer("id").primaryKey({ autoIncrement: true }),
- name: text("name").notNull().unique(),
+export const tags = sqliteTable('tags', {
+ id: integer('id').primaryKey({ autoIncrement: true }),
+ name: text('name').notNull().unique()
});
export const announcementTags = sqliteTable(
- "announcement_tags",
- {
- announcementId: integer("announcement_id")
- .notNull()
- .references(() => announcements.id, { onDelete: "cascade" }),
- tagId: integer("tag_id")
- .notNull()
- .references(() => tags.id, { onDelete: "cascade" }),
- },
- (table) => ({
- pk: primaryKey({ columns: [table.announcementId, table.tagId] }),
- tagIdIdx: index("announcement_tags_tag_id_idx").on(table.tagId),
- }),
+ 'announcement_tags',
+ {
+ announcementId: integer('announcement_id')
+ .notNull()
+ .references(() => announcements.id, { onDelete: 'cascade' }),
+ tagId: integer('tag_id')
+ .notNull()
+ .references(() => tags.id, { onDelete: 'cascade' })
+ },
+ (table) => ({
+ pk: primaryKey({ columns: [table.announcementId, table.tagId] }),
+ tagIdIdx: index('announcement_tags_tag_id_idx').on(table.tagId)
+ })
);
diff --git a/src/index.ts b/src/index.ts
index 4f1863f4..a4b6a902 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,91 +1,98 @@
-import { OpenAPIHono } from "@hono/zod-openapi";
-import { swaggerUI } from "@hono/swagger-ui";
-import type { Env } from "./types";
-import { cacheControl, CacheDuration } from "./cache";
-import { getConfig } from "./config";
-import packageJson from "../package.json";
-import patchesApp from "./routes/patches";
-import managerApp from "./routes/manager";
-import announcementsApp from "./routes/announcements";
-import contributorsApp from "./routes/contributors";
-import teamApp from "./routes/team";
-import aboutApp from "./routes/about";
+import { OpenAPIHono } from '@hono/zod-openapi';
+import { swaggerUI } from '@hono/swagger-ui';
+import type { Env } from './types';
+import { cacheControl, CacheDuration } from './cache';
+import { getConfig } from './config';
+import packageJson from '../package.json';
+import patchesApp from './routes/patches';
+import managerApp from './routes/manager';
+import announcementsApp from './routes/announcements';
+import contributorsApp from './routes/contributors';
+import teamApp from './routes/team';
+import aboutApp from './routes/about';
type AppBindings = { Bindings: Env };
let _app: OpenAPIHono | undefined;
export default {
- async fetch(
- request: Request,
- env: Env,
- ctx: ExecutionContext,
- ): Promise {
- if (!_app) {
- const { apiVersion } = getConfig(env);
+ async fetch(
+ request: Request,
+ env: Env,
+ ctx: ExecutionContext
+ ): Promise {
+ if (!_app) {
+ const { apiVersion } = getConfig(env);
- _app = new OpenAPIHono();
+ _app = new OpenAPIHono();
- _app.onError((err, c) => {
- console.error(err);
- return c.json(
- {
- error: err.message || "Unknown error",
- stack: err.stack,
- },
- 500,
- );
- });
+ _app.onError((err, c) => {
+ console.error(err);
+ return c.json(
+ {
+ error: err.message || 'Unknown error',
+ stack: err.stack
+ },
+ 500
+ );
+ });
- // Default 5-minute cache for all routes (overridden per-route where needed)
- _app.use("*", cacheControl(CacheDuration.short));
+ // Default 5-minute cache for all routes (overridden per-route where needed)
+ _app.use('*', cacheControl(CacheDuration.short));
- const versionedApp = new OpenAPIHono();
- versionedApp.route("/patches", patchesApp);
- versionedApp.route("/manager", managerApp);
- versionedApp.route("/announcements", announcementsApp);
- versionedApp.route("/contributors", contributorsApp);
- versionedApp.route("/team", teamApp);
- versionedApp.route("/about", aboutApp);
+ const versionedApp = new OpenAPIHono();
+ versionedApp.route('/patches', patchesApp);
+ versionedApp.route('/manager', managerApp);
+ versionedApp.route('/announcements', announcementsApp);
+ versionedApp.route('/contributors', contributorsApp);
+ versionedApp.route('/team', teamApp);
+ versionedApp.route('/about', aboutApp);
- _app.route(`/v${apiVersion}`, versionedApp);
- _app.get("/", swaggerUI({ url: `/v${apiVersion}/openapi` }));
+ _app.route(`/v${apiVersion}`, versionedApp);
+ _app.get('/', swaggerUI({ url: `/v${apiVersion}/openapi` }));
- _app.doc(`/v${apiVersion}/openapi`, () => ({
- openapi: "3.1.0",
- info: {
- title: "ReVanced API",
- version: packageJson.version,
- description: "API server for ReVanced.",
- contact: {
- name: "ReVanced",
- url: "https://revanced.app",
- email: "contact@revanced.app",
- },
- license: {
- name: "AGPLv3",
- url: "https://github.com/ReVanced/revanced-api/blob/main/LICENSE",
- },
- },
- servers: [
- { url: "https://api.revanced.app", description: "Production" },
- {
- url: "{customServer}",
- description: "Custom server",
- variables: {
- customServer: {
- default: "api.revanced.app",
- description: "Custom server URL",
- },
- },
- },
- ],
- }));
- _app.openAPIRegistry.registerComponent("securitySchemes", "Bearer", {
- type: "http",
- scheme: "bearer",
- });
- }
- return _app.fetch(request, env, ctx);
- },
+ _app.doc(`/v${apiVersion}/openapi`, () => ({
+ openapi: '3.1.0',
+ info: {
+ title: 'ReVanced API',
+ version: packageJson.version,
+ description: 'API server for ReVanced.',
+ contact: {
+ name: 'ReVanced',
+ url: 'https://revanced.app',
+ email: 'contact@revanced.app'
+ },
+ license: {
+ name: 'AGPLv3',
+ url: 'https://github.com/ReVanced/revanced-api/blob/main/LICENSE'
+ }
+ },
+ servers: [
+ {
+ url: 'https://api.revanced.app',
+ description: 'Production'
+ },
+ {
+ url: '{customServer}',
+ description: 'Custom server',
+ variables: {
+ customServer: {
+ default: 'api.revanced.app',
+ description: 'Custom server URL'
+ }
+ }
+ }
+ ]
+ }));
+ _app.openAPIRegistry.registerComponent(
+ 'securitySchemes',
+ 'Bearer',
+ {
+ type: 'http',
+ scheme: 'bearer'
+ }
+ );
+ }
+ return _app.fetch(request, env, ctx);
+ }
};
diff --git a/src/routes/about.ts b/src/routes/about.ts
index 28856797..4bcbcfad 100644
--- a/src/routes/about.ts
+++ b/src/routes/about.ts
@@ -1,87 +1,89 @@
-import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
-import type { Env } from "../types";
-import aboutData from "about.json";
-import { cacheControl, CacheDuration } from "../cache";
+import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi';
+import type { Env } from '../types';
+import aboutData from 'about.json';
+import { cacheControl, CacheDuration } from '../cache';
const app = new OpenAPIHono<{ Bindings: Env }>();
// 1-day cache for about info
-app.use("*", cacheControl(CacheDuration.day));
+app.use('*', cacheControl(CacheDuration.day));
const AboutResponseSchema = z
- .object({
- name: z.string(),
- about: z.string(),
- keys: z.string(),
- branding: z
- .object({
- logo: z.string().url(),
- })
- .optional()
- .nullable(),
- status: z.string(),
- contact: z
- .object({
- email: z.string().email(),
- })
- .optional()
- .nullable(),
- socials: z
- .array(
- z.object({
- name: z.string(),
- url: z.string().url(),
- preferred: z.boolean().optional(),
- }),
- )
- .optional()
- .nullable(),
- donations: z
- .object({
- wallets: z
- .array(
- z.object({
- network: z.string(),
- currency_code: z.string(),
- address: z.string(),
- preferred: z.boolean().optional(),
- }),
- )
- .optional()
- .nullable(),
- links: z
- .array(
- z.object({
- name: z.string(),
- url: z.string().url(),
- preferred: z.boolean().optional(),
- }),
- )
- .optional()
- .nullable(),
- })
- .optional()
- .nullable(),
- })
- .openapi("About");
+ .object({
+ name: z.string(),
+ about: z.string(),
+ keys: z.string(),
+ branding: z
+ .object({
+ logo: z.string().url()
+ })
+ .optional()
+ .nullable(),
+ status: z.string(),
+ contact: z
+ .object({
+ email: z.string().email()
+ })
+ .optional()
+ .nullable(),
+ socials: z
+ .array(
+ z.object({
+ name: z.string(),
+ url: z.string().url(),
+ preferred: z.boolean().optional()
+ })
+ )
+ .optional()
+ .nullable(),
+ donations: z
+ .object({
+ wallets: z
+ .array(
+ z.object({
+ network: z.string(),
+ currency_code: z.string(),
+ address: z.string(),
+ preferred: z.boolean().optional()
+ })
+ )
+ .optional()
+ .nullable(),
+ links: z
+ .array(
+ z.object({
+ name: z.string(),
+ url: z.string().url(),
+ preferred: z.boolean().optional()
+ })
+ )
+ .optional()
+ .nullable()
+ })
+ .optional()
+ .nullable()
+ })
+ .openapi('About');
app.openapi(
- createRoute({
- method: "get",
- path: "/",
- tags: ["API"],
- summary: "Get about",
- description: "Get information about the API.",
- responses: {
- 200: {
- content: { "application/json": { schema: AboutResponseSchema } },
- description: "Information about the API.",
- },
- },
- }),
- (c) => {
- return c.json(aboutData as z.infer, 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/',
+ tags: ['API'],
+ summary: 'Get about',
+ description: 'Get information about the API.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: AboutResponseSchema }
+ },
+ description: 'Information about the API.'
+ }
+ }
+ }),
+ (c) => {
+ return c.json(aboutData as z.infer, 200);
+ }
);
export default app;
diff --git a/src/routes/announcements.ts b/src/routes/announcements.ts
index 75fe642a..5670acfd 100644
--- a/src/routes/announcements.ts
+++ b/src/routes/announcements.ts
@@ -1,219 +1,246 @@
-import { OpenAPIHono, createRoute } from "@hono/zod-openapi";
-import type { Env } from "../types";
-import { authMiddleware } from "../auth/auth";
-import { ErrorResponseSchema } from "../schemas/common";
+import { OpenAPIHono, createRoute } from '@hono/zod-openapi';
+import type { Env } from '../types';
+import { authMiddleware } from '../auth/auth';
+import { ErrorResponseSchema } from '../schemas/common';
import {
- AnnouncementIdParamSchema,
- AnnouncementResponseSchema,
- AnnouncementsResponseSchema,
- CreateAnnouncementBodySchema,
- LatestAnnouncementIdsResponseSchema,
- LatestAnnouncementsResponseSchema,
- UpdateAnnouncementBodySchema,
-} from "../schemas/announcements";
-import * as announcementsService from "../services/announcements";
+ AnnouncementIdParamSchema,
+ AnnouncementResponseSchema,
+ AnnouncementsResponseSchema,
+ CreateAnnouncementBodySchema,
+ LatestAnnouncementIdsResponseSchema,
+ LatestAnnouncementsResponseSchema,
+ UpdateAnnouncementBodySchema
+} from '../schemas/announcements';
+import * as announcementsService from '../services/announcements';
const app = new OpenAPIHono<{ Bindings: Env }>();
app.openapi(
- createRoute({
- method: "get",
- path: "/",
- tags: ["Announcements"],
- summary: "Get all announcements",
- description: "Get all announcements ordered by newest first.",
- responses: {
- 200: {
- content: {
- "application/json": { schema: AnnouncementsResponseSchema },
- },
- description: "All announcements.",
- },
- },
- }),
- async (c) => {
- return c.json(await announcementsService.listAnnouncements(c.env), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/',
+ tags: ['Announcements'],
+ summary: 'Get all announcements',
+ description: 'Get all announcements ordered by newest first.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: AnnouncementsResponseSchema }
+ },
+ description: 'All announcements.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await announcementsService.listAnnouncements(c.env), 200);
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/latest",
- tags: ["Announcements"],
- summary: "Get the latest announcement for each tag",
- description:
- "Get the newest announcement for every available announcement tag.",
- responses: {
- 200: {
- content: {
- "application/json": { schema: LatestAnnouncementsResponseSchema },
- },
- description: "The newest announcement for each tag.",
- },
- },
- }),
- async (c) => {
- return c.json(
- await announcementsService.getLatestAnnouncementsByTag(c.env),
- 200,
- );
- },
+ createRoute({
+ method: 'get',
+ path: '/latest',
+ tags: ['Announcements'],
+ summary: 'Get the latest announcement for each tag',
+ description:
+ 'Get the newest announcement for every available announcement tag.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': {
+ schema: LatestAnnouncementsResponseSchema
+ }
+ },
+ description: 'The newest announcement for each tag.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(
+ await announcementsService.getLatestAnnouncementsByTag(c.env),
+ 200
+ );
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/latest/id",
- tags: ["Announcements"],
- summary: "Get the latest announcement ID for each tag",
- description:
- "Get the ID of the newest announcement for every available announcement tag.",
- responses: {
- 200: {
- content: {
- "application/json": { schema: LatestAnnouncementIdsResponseSchema },
- },
- description: "The newest announcement ID for each tag.",
- },
- },
- }),
- async (c) => {
- return c.json(
- await announcementsService.getLatestAnnouncementIdsByTag(c.env),
- 200,
- );
- },
+ createRoute({
+ method: 'get',
+ path: '/latest/id',
+ tags: ['Announcements'],
+ summary: 'Get the latest announcement ID for each tag',
+ description:
+ 'Get the ID of the newest announcement for every available announcement tag.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': {
+ schema: LatestAnnouncementIdsResponseSchema
+ }
+ },
+ description: 'The newest announcement ID for each tag.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(
+ await announcementsService.getLatestAnnouncementIdsByTag(c.env),
+ 200
+ );
+ }
);
app.openapi(
- createRoute({
- method: "post",
- path: "/",
- tags: ["Announcements"],
- summary: "Create an announcement",
- description:
- "Create a new announcement. Requires bearer token authentication.",
- security: [{ Bearer: [] }],
- middleware: [authMiddleware],
- request: {
- body: {
- content: {
- "application/json": { schema: CreateAnnouncementBodySchema },
- },
- },
- },
- responses: {
- 201: {
- content: { "application/json": { schema: AnnouncementResponseSchema } },
- description: "The created announcement.",
- },
- 401: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "Unauthorized.",
- },
- 403: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "Forbidden.",
- },
- },
- }),
- async (c) => {
- const body = c.req.valid("json");
- return c.json(
- await announcementsService.createAnnouncement(c.env, body),
- 201,
- );
- },
+ createRoute({
+ method: 'post',
+ path: '/',
+ tags: ['Announcements'],
+ summary: 'Create an announcement',
+ description:
+ 'Create a new announcement. Requires bearer token authentication.',
+ security: [{ Bearer: [] }],
+ middleware: [authMiddleware],
+ request: {
+ body: {
+ content: {
+ 'application/json': { schema: CreateAnnouncementBodySchema }
+ }
+ }
+ },
+ responses: {
+ 201: {
+ content: {
+ 'application/json': { schema: AnnouncementResponseSchema }
+ },
+ description: 'The created announcement.'
+ },
+ 401: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'Unauthorized.'
+ },
+ 403: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'Forbidden.'
+ }
+ }
+ }),
+ async (c) => {
+ const body = c.req.valid('json');
+ return c.json(
+ await announcementsService.createAnnouncement(c.env, body),
+ 201
+ );
+ }
);
app.openapi(
- createRoute({
- method: "patch",
- path: "/{id}",
- tags: ["Announcements"],
- summary: "Update an announcement",
- description:
- "Update an existing announcement. Requires bearer token authentication.",
- security: [{ Bearer: [] }],
- middleware: [authMiddleware],
- request: {
- params: AnnouncementIdParamSchema,
- body: {
- content: {
- "application/json": { schema: UpdateAnnouncementBodySchema },
- },
- },
- },
- responses: {
- 200: {
- content: { "application/json": { schema: AnnouncementResponseSchema } },
- description: "The updated announcement.",
- },
- 401: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "Unauthorized.",
- },
- 403: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "Forbidden.",
- },
- 404: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "Announcement not found.",
- },
- },
- }),
- async (c) => {
- const { id } = c.req.valid("param");
- const body = c.req.valid("json");
- const result = await announcementsService.updateAnnouncement(
- c.env,
- id,
- body,
- );
- if (!result) {
- return c.json({ error: "Announcement not found" }, 404);
- }
- return c.json(result, 200);
- },
+ createRoute({
+ method: 'patch',
+ path: '/{id}',
+ tags: ['Announcements'],
+ summary: 'Update an announcement',
+ description:
+ 'Update an existing announcement. Requires bearer token authentication.',
+ security: [{ Bearer: [] }],
+ middleware: [authMiddleware],
+ request: {
+ params: AnnouncementIdParamSchema,
+ body: {
+ content: {
+ 'application/json': { schema: UpdateAnnouncementBodySchema }
+ }
+ }
+ },
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: AnnouncementResponseSchema }
+ },
+ description: 'The updated announcement.'
+ },
+ 401: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'Unauthorized.'
+ },
+ 403: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'Forbidden.'
+ },
+ 404: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'Announcement not found.'
+ }
+ }
+ }),
+ async (c) => {
+ const { id } = c.req.valid('param');
+ const body = c.req.valid('json');
+ const result = await announcementsService.updateAnnouncement(
+ c.env,
+ id,
+ body
+ );
+ if (!result) {
+ return c.json({ error: 'Announcement not found' }, 404);
+ }
+ return c.json(result, 200);
+ }
);
app.openapi(
- createRoute({
- method: "delete",
- path: "/{id}",
- tags: ["Announcements"],
- summary: "Delete an announcement",
- description:
- "Delete an announcement. Requires bearer token authentication.",
- security: [{ Bearer: [] }],
- middleware: [authMiddleware],
- request: { params: AnnouncementIdParamSchema },
- responses: {
- 204: { description: "Announcement deleted." },
- 401: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "Unauthorized.",
- },
- 403: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "Forbidden.",
- },
- 404: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "Announcement not found.",
- },
- },
- }),
- async (c) => {
- const { id } = c.req.valid("param");
- const deleted = await announcementsService.deleteAnnouncement(c.env, id);
- if (!deleted) {
- return c.json({ error: "Announcement not found" }, 404);
- }
- return c.body(null, 204);
- },
+ createRoute({
+ method: 'delete',
+ path: '/{id}',
+ tags: ['Announcements'],
+ summary: 'Delete an announcement',
+ description:
+ 'Delete an announcement. Requires bearer token authentication.',
+ security: [{ Bearer: [] }],
+ middleware: [authMiddleware],
+ request: { params: AnnouncementIdParamSchema },
+ responses: {
+ 204: { description: 'Announcement deleted.' },
+ 401: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'Unauthorized.'
+ },
+ 403: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'Forbidden.'
+ },
+ 404: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'Announcement not found.'
+ }
+ }
+ }),
+ async (c) => {
+ const { id } = c.req.valid('param');
+ const deleted = await announcementsService.deleteAnnouncement(
+ c.env,
+ id
+ );
+ if (!deleted) {
+ return c.json({ error: 'Announcement not found' }, 404);
+ }
+ return c.body(null, 204);
+ }
);
export default app;
diff --git a/src/routes/contributors.ts b/src/routes/contributors.ts
index 981dc00a..6631991b 100644
--- a/src/routes/contributors.ts
+++ b/src/routes/contributors.ts
@@ -1,36 +1,41 @@
-import { OpenAPIHono, createRoute } from "@hono/zod-openapi";
-import type { Env } from "../types";
-import { ErrorResponseSchema } from "../schemas/common";
-import { ContributorsResponseSchema } from "../schemas/contributors";
-import * as contributorsService from "../services/contributors";
-import { cacheControl, CacheDuration } from "../cache";
+import { OpenAPIHono, createRoute } from '@hono/zod-openapi';
+import type { Env } from '../types';
+import { ErrorResponseSchema } from '../schemas/common';
+import { ContributorsResponseSchema } from '../schemas/contributors';
+import * as contributorsService from '../services/contributors';
+import { cacheControl, CacheDuration } from '../cache';
const app = new OpenAPIHono<{ Bindings: Env }>();
// 1-day cache for contributors
-app.use("*", cacheControl(CacheDuration.day));
+app.use('*', cacheControl(CacheDuration.day));
app.openapi(
- createRoute({
- method: "get",
- path: "/",
- tags: ["API"],
- summary: "Get contributors",
- description: "Get the list of contributors for each configured repository.",
- responses: {
- 200: {
- content: { "application/json": { schema: ContributorsResponseSchema } },
- description: "The list of contributors.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await contributorsService.getContributors(c.env), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/',
+ tags: ['API'],
+ summary: 'Get contributors',
+ description:
+ 'Get the list of contributors for each configured repository.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: ContributorsResponseSchema }
+ },
+ description: 'The list of contributors.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await contributorsService.getContributors(c.env), 200);
+ }
);
export default app;
diff --git a/src/routes/manager.ts b/src/routes/manager.ts
index 003bf805..c989b974 100644
--- a/src/routes/manager.ts
+++ b/src/routes/manager.ts
@@ -1,249 +1,297 @@
-import { OpenAPIHono, createRoute } from "@hono/zod-openapi";
-import type { Env } from "../types";
-import { ErrorResponseSchema } from "../schemas/common";
+import { OpenAPIHono, createRoute } from '@hono/zod-openapi';
+import type { Env } from '../types';
+import { ErrorResponseSchema } from '../schemas/common';
import {
- ReleaseResponseSchema,
- VersionResponseSchema,
- HistoryResponseSchema,
-} from "../schemas/releases";
-import * as managerService from "../services/manager";
+ ReleaseResponseSchema,
+ VersionResponseSchema,
+ HistoryResponseSchema
+} from '../schemas/releases';
+import * as managerService from '../services/manager';
const app = new OpenAPIHono<{ Bindings: Env }>();
app.openapi(
- createRoute({
- method: "get",
- path: "/",
- tags: ["Manager"],
- summary: "Get current manager release",
- description: "Get the current stable manager release.",
- responses: {
- 200: {
- content: { "application/json": { schema: ReleaseResponseSchema } },
- description: "The latest manager release.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await managerService.getRelease(c.env, false), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/',
+ tags: ['Manager'],
+ summary: 'Get current manager release',
+ description: 'Get the current stable manager release.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: ReleaseResponseSchema }
+ },
+ description: 'The latest manager release.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await managerService.getRelease(c.env, false), 200);
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/prerelease",
- tags: ["Manager"],
- summary: "Get current manager prerelease",
- description: "Get the current manager prerelease.",
- responses: {
- 200: {
- content: { "application/json": { schema: ReleaseResponseSchema } },
- description: "The latest manager prerelease.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await managerService.getRelease(c.env, true), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/prerelease',
+ tags: ['Manager'],
+ summary: 'Get current manager prerelease',
+ description: 'Get the current manager prerelease.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: ReleaseResponseSchema }
+ },
+ description: 'The latest manager prerelease.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await managerService.getRelease(c.env, true), 200);
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/version",
- tags: ["Manager"],
- summary: "Get current manager release version",
- description: "Get the current stable manager release version.",
- responses: {
- 200: {
- content: { "application/json": { schema: VersionResponseSchema } },
- description: "The current manager release version.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await managerService.getVersion(c.env, false), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/version',
+ tags: ['Manager'],
+ summary: 'Get current manager release version',
+ description: 'Get the current stable manager release version.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: VersionResponseSchema }
+ },
+ description: 'The current manager release version.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await managerService.getVersion(c.env, false), 200);
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/version/prerelease",
- tags: ["Manager"],
- summary: "Get current manager prerelease version",
- description: "Get the current manager prerelease version.",
- responses: {
- 200: {
- content: { "application/json": { schema: VersionResponseSchema } },
- description: "The current manager prerelease version.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await managerService.getVersion(c.env, true), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/version/prerelease',
+ tags: ['Manager'],
+ summary: 'Get current manager prerelease version',
+ description: 'Get the current manager prerelease version.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: VersionResponseSchema }
+ },
+ description: 'The current manager prerelease version.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await managerService.getVersion(c.env, true), 200);
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/history",
- tags: ["Manager"],
- summary: "Get manager release history",
- description: "Get the stable manager release history.",
- responses: {
- 200: {
- content: { "application/json": { schema: HistoryResponseSchema } },
- description: "The manager release history.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await managerService.getHistory(c.env, false), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/history',
+ tags: ['Manager'],
+ summary: 'Get manager release history',
+ description: 'Get the stable manager release history.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: HistoryResponseSchema }
+ },
+ description: 'The manager release history.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await managerService.getHistory(c.env, false), 200);
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/history/prerelease",
- tags: ["Manager"],
- summary: "Get manager prerelease history",
- description: "Get the manager prerelease history.",
- responses: {
- 200: {
- content: { "application/json": { schema: HistoryResponseSchema } },
- description: "The manager prerelease history.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await managerService.getHistory(c.env, true), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/history/prerelease',
+ tags: ['Manager'],
+ summary: 'Get manager prerelease history',
+ description: 'Get the manager prerelease history.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: HistoryResponseSchema }
+ },
+ description: 'The manager prerelease history.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await managerService.getHistory(c.env, true), 200);
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/downloaders",
- tags: ["Manager"],
- summary: "Get current manager downloaders release",
- description: "Get the current stable manager downloaders release.",
- responses: {
- 200: {
- content: { "application/json": { schema: ReleaseResponseSchema } },
- description: "The latest manager downloaders release.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(
- await managerService.getDownloadersRelease(c.env, false),
- 200,
- );
- },
+ createRoute({
+ method: 'get',
+ path: '/downloaders',
+ tags: ['Manager'],
+ summary: 'Get current manager downloaders release',
+ description: 'Get the current stable manager downloaders release.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: ReleaseResponseSchema }
+ },
+ description: 'The latest manager downloaders release.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(
+ await managerService.getDownloadersRelease(c.env, false),
+ 200
+ );
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/downloaders/prerelease",
- tags: ["Manager"],
- summary: "Get current manager downloaders prerelease",
- description: "Get the current manager downloaders prerelease.",
- responses: {
- 200: {
- content: { "application/json": { schema: ReleaseResponseSchema } },
- description: "The latest manager downloaders prerelease.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await managerService.getDownloadersRelease(c.env, true), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/downloaders/prerelease',
+ tags: ['Manager'],
+ summary: 'Get current manager downloaders prerelease',
+ description: 'Get the current manager downloaders prerelease.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: ReleaseResponseSchema }
+ },
+ description: 'The latest manager downloaders prerelease.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(
+ await managerService.getDownloadersRelease(c.env, true),
+ 200
+ );
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/downloaders/version",
- tags: ["Manager"],
- summary: "Get current manager downloaders release version",
- description: "Get the current stable manager downloaders release version.",
- responses: {
- 200: {
- content: { "application/json": { schema: VersionResponseSchema } },
- description: "The current manager downloaders release version.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(
- await managerService.getDownloadersVersion(c.env, false),
- 200,
- );
- },
+ createRoute({
+ method: 'get',
+ path: '/downloaders/version',
+ tags: ['Manager'],
+ summary: 'Get current manager downloaders release version',
+ description:
+ 'Get the current stable manager downloaders release version.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: VersionResponseSchema }
+ },
+ description: 'The current manager downloaders release version.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(
+ await managerService.getDownloadersVersion(c.env, false),
+ 200
+ );
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/downloaders/version/prerelease",
- tags: ["Manager"],
- summary: "Get current manager downloaders prerelease version",
- description: "Get the current manager downloaders prerelease version.",
- responses: {
- 200: {
- content: { "application/json": { schema: VersionResponseSchema } },
- description: "The current manager downloaders prerelease version.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await managerService.getDownloadersVersion(c.env, true), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/downloaders/version/prerelease',
+ tags: ['Manager'],
+ summary: 'Get current manager downloaders prerelease version',
+ description: 'Get the current manager downloaders prerelease version.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: VersionResponseSchema }
+ },
+ description:
+ 'The current manager downloaders prerelease version.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(
+ await managerService.getDownloadersVersion(c.env, true),
+ 200
+ );
+ }
);
export default app;
diff --git a/src/routes/patches.ts b/src/routes/patches.ts
index 72e6a53c..8adc5c4b 100644
--- a/src/routes/patches.ts
+++ b/src/routes/patches.ts
@@ -1,173 +1,199 @@
-import { OpenAPIHono, createRoute } from "@hono/zod-openapi";
-import type { Env } from "../types";
-import { ErrorResponseSchema } from "../schemas/common";
+import { OpenAPIHono, createRoute } from '@hono/zod-openapi';
+import type { Env } from '../types';
+import { ErrorResponseSchema } from '../schemas/common';
import {
- ReleaseResponseSchema,
- VersionResponseSchema,
- HistoryResponseSchema,
- PublicKeyResponseSchema,
-} from "../schemas/releases";
-import * as patchesService from "../services/patches";
+ ReleaseResponseSchema,
+ VersionResponseSchema,
+ HistoryResponseSchema,
+ PublicKeyResponseSchema
+} from '../schemas/releases';
+import * as patchesService from '../services/patches';
const app = new OpenAPIHono<{ Bindings: Env }>();
app.openapi(
- createRoute({
- method: "get",
- path: "/",
- tags: ["Patches"],
- summary: "Get current patches release",
- description: "Get the current stable patches release.",
- responses: {
- 200: {
- content: { "application/json": { schema: ReleaseResponseSchema } },
- description: "The current patches release.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await patchesService.getRelease(c.env, false), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/',
+ tags: ['Patches'],
+ summary: 'Get current patches release',
+ description: 'Get the current stable patches release.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: ReleaseResponseSchema }
+ },
+ description: 'The current patches release.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await patchesService.getRelease(c.env, false), 200);
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/prerelease",
- tags: ["Patches"],
- summary: "Get current patches prerelease",
- description: "Get the current patches prerelease.",
- responses: {
- 200: {
- content: { "application/json": { schema: ReleaseResponseSchema } },
- description: "The current patches prerelease.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await patchesService.getRelease(c.env, true), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/prerelease',
+ tags: ['Patches'],
+ summary: 'Get current patches prerelease',
+ description: 'Get the current patches prerelease.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: ReleaseResponseSchema }
+ },
+ description: 'The current patches prerelease.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await patchesService.getRelease(c.env, true), 200);
+ }
);
// --- Version ---
app.openapi(
- createRoute({
- method: "get",
- path: "/version",
- tags: ["Patches"],
- summary: "Get current patches release version",
- description: "Get the current stable patches release version.",
- responses: {
- 200: {
- content: { "application/json": { schema: VersionResponseSchema } },
- description: "The current patches release version.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await patchesService.getVersion(c.env, false), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/version',
+ tags: ['Patches'],
+ summary: 'Get current patches release version',
+ description: 'Get the current stable patches release version.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: VersionResponseSchema }
+ },
+ description: 'The current patches release version.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await patchesService.getVersion(c.env, false), 200);
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/version/prerelease",
- tags: ["Patches"],
- summary: "Get current patches prerelease version",
- description: "Get the current patches prerelease version.",
- responses: {
- 200: {
- content: { "application/json": { schema: VersionResponseSchema } },
- description: "The current patches prerelease version.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await patchesService.getVersion(c.env, true), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/version/prerelease',
+ tags: ['Patches'],
+ summary: 'Get current patches prerelease version',
+ description: 'Get the current patches prerelease version.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: VersionResponseSchema }
+ },
+ description: 'The current patches prerelease version.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await patchesService.getVersion(c.env, true), 200);
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/history",
- tags: ["Patches"],
- summary: "Get patches release history",
- description: "Get the stable patches release history.",
- responses: {
- 200: {
- content: { "application/json": { schema: HistoryResponseSchema } },
- description: "The patches release history.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await patchesService.getHistory(c.env, false), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/history',
+ tags: ['Patches'],
+ summary: 'Get patches release history',
+ description: 'Get the stable patches release history.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: HistoryResponseSchema }
+ },
+ description: 'The patches release history.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await patchesService.getHistory(c.env, false), 200);
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/history/prerelease",
- tags: ["Patches"],
- summary: "Get patches prerelease history",
- description: "Get the patches prerelease history.",
- responses: {
- 200: {
- content: { "application/json": { schema: HistoryResponseSchema } },
- description: "The patches prerelease history.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await patchesService.getHistory(c.env, true), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/history/prerelease',
+ tags: ['Patches'],
+ summary: 'Get patches prerelease history',
+ description: 'Get the patches prerelease history.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: HistoryResponseSchema }
+ },
+ description: 'The patches prerelease history.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await patchesService.getHistory(c.env, true), 200);
+ }
);
app.openapi(
- createRoute({
- method: "get",
- path: "/keys",
- tags: ["Patches"],
- summary: "Get patches public keys",
- description: "Get the public keys for verifying patches assets.",
- responses: {
- 200: {
- content: { "application/json": { schema: PublicKeyResponseSchema } },
- description: "The public keys.",
- },
- },
- }),
- async (c) => {
- return c.json(await patchesService.getPublicKey(c.env), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/keys',
+ tags: ['Patches'],
+ summary: 'Get patches public keys',
+ description: 'Get the public keys for verifying patches assets.',
+ responses: {
+ 200: {
+ content: {
+ 'application/json': { schema: PublicKeyResponseSchema }
+ },
+ description: 'The public keys.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await patchesService.getPublicKey(c.env), 200);
+ }
);
export default app;
diff --git a/src/routes/team.ts b/src/routes/team.ts
index a2366381..f6dbaf97 100644
--- a/src/routes/team.ts
+++ b/src/routes/team.ts
@@ -1,36 +1,38 @@
-import { OpenAPIHono, createRoute } from "@hono/zod-openapi";
-import type { Env } from "../types";
-import { ErrorResponseSchema } from "../schemas/common";
-import { TeamResponseSchema } from "../schemas/contributors";
-import * as teamService from "../services/team";
-import { cacheControl, CacheDuration } from "../cache";
+import { OpenAPIHono, createRoute } from '@hono/zod-openapi';
+import type { Env } from '../types';
+import { ErrorResponseSchema } from '../schemas/common';
+import { TeamResponseSchema } from '../schemas/contributors';
+import * as teamService from '../services/team';
+import { cacheControl, CacheDuration } from '../cache';
const app = new OpenAPIHono<{ Bindings: Env }>();
// 1-day cache for team members
-app.use("*", cacheControl(CacheDuration.day));
+app.use('*', cacheControl(CacheDuration.day));
app.openapi(
- createRoute({
- method: "get",
- path: "/",
- tags: ["API"],
- summary: "Get team members",
- description: "Get the list of team members from the organization.",
- responses: {
- 200: {
- content: { "application/json": { schema: TeamResponseSchema } },
- description: "The list of team members.",
- },
- 500: {
- content: { "application/json": { schema: ErrorResponseSchema } },
- description: "GitHub API error.",
- },
- },
- }),
- async (c) => {
- return c.json(await teamService.getTeamMembers(c.env), 200);
- },
+ createRoute({
+ method: 'get',
+ path: '/',
+ tags: ['API'],
+ summary: 'Get team members',
+ description: 'Get the list of team members from the organization.',
+ responses: {
+ 200: {
+ content: { 'application/json': { schema: TeamResponseSchema } },
+ description: 'The list of team members.'
+ },
+ 500: {
+ content: {
+ 'application/json': { schema: ErrorResponseSchema }
+ },
+ description: 'GitHub API error.'
+ }
+ }
+ }),
+ async (c) => {
+ return c.json(await teamService.getTeamMembers(c.env), 200);
+ }
);
export default app;
diff --git a/src/schemas/announcements.ts b/src/schemas/announcements.ts
index a2264d08..e0a7e92d 100644
--- a/src/schemas/announcements.ts
+++ b/src/schemas/announcements.ts
@@ -1,89 +1,89 @@
-import { z } from "@hono/zod-openapi";
+import { z } from '@hono/zod-openapi';
export const AnnouncementIdParamSchema = z.object({
- id: z.coerce
- .number()
- .int()
- .openapi({
- description: "Announcement ID.",
- example: 1,
- param: { in: "path", required: true },
- }),
+ id: z.coerce
+ .number()
+ .int()
+ .openapi({
+ description: 'Announcement ID.',
+ example: 1,
+ param: { in: 'path', required: true }
+ })
});
export const AnnouncementResponseSchema = z
- .object({
- id: z.number().int().openapi({ example: 1 }),
- author: z.string().nullable().openapi({ example: "ReVanced" }),
- title: z.string().openapi({ example: "Welcome" }),
- content: z.string().nullable().openapi({ example: "Some content" }),
- tags: z.array(z.string()).openapi({ example: ["Important"] }),
- created_at: z.iso
- .datetime()
- .openapi({ example: "1970-01-01T00:00:00.000Z" }),
- archived_at: z.iso.datetime().nullable().openapi({ example: null }),
- level: z.number().int().openapi({ example: 0 }),
- })
- .openapi("Announcement");
+ .object({
+ id: z.number().int().openapi({ example: 1 }),
+ author: z.string().nullable().openapi({ example: 'ReVanced' }),
+ title: z.string().openapi({ example: 'Welcome' }),
+ content: z.string().nullable().openapi({ example: 'Some content' }),
+ tags: z.array(z.string()).openapi({ example: ['Important'] }),
+ created_at: z.iso
+ .datetime()
+ .openapi({ example: '1970-01-01T00:00:00.000Z' }),
+ archived_at: z.iso.datetime().nullable().openapi({ example: null }),
+ level: z.number().int().openapi({ example: 0 })
+ })
+ .openapi('Announcement');
export const AnnouncementsResponseSchema = z.array(AnnouncementResponseSchema);
export const LatestAnnouncementEntrySchema = z
- .object({
- tag: z.string().nullable().openapi({ example: "Important" }),
- announcement: AnnouncementResponseSchema,
- })
- .openapi("LatestAnnouncementEntry");
+ .object({
+ tag: z.string().nullable().openapi({ example: 'Important' }),
+ announcement: AnnouncementResponseSchema
+ })
+ .openapi('LatestAnnouncementEntry');
export const LatestAnnouncementsResponseSchema = z
- .array(LatestAnnouncementEntrySchema)
- .openapi("LatestAnnouncementsByTag");
+ .array(LatestAnnouncementEntrySchema)
+ .openapi('LatestAnnouncementsByTag');
export const LatestAnnouncementIdEntrySchema = z
- .object({
- tag: z.string().nullable().openapi({ example: "Important" }),
- id: z.number().int().openapi({ example: 1 }),
- })
- .openapi("LatestAnnouncementIdEntry");
+ .object({
+ tag: z.string().nullable().openapi({ example: 'Important' }),
+ id: z.number().int().openapi({ example: 1 })
+ })
+ .openapi('LatestAnnouncementIdEntry');
export const LatestAnnouncementIdsResponseSchema = z
- .array(LatestAnnouncementIdEntrySchema)
- .openapi("LatestAnnouncementIdsByTag");
+ .array(LatestAnnouncementIdEntrySchema)
+ .openapi('LatestAnnouncementIdsByTag');
export const CreateAnnouncementBodySchema = z
- .object({
- author: z.string().optional().openapi({ example: "ReVanced" }),
- title: z.string().openapi({ example: "Welcome" }),
- content: z.string().optional().openapi({ example: "Some content" }),
- tags: z.array(z.string()).openapi({ example: ["Important"] }),
- created_at: z.string().datetime().nullable().optional().openapi({
- example: "1970-01-01T00:00:00.000Z",
- description: "UTC timestamp. Defaults to current time if omitted.",
- }),
- archived_at: z.iso
- .datetime()
- .nullable()
- .optional()
- .openapi({ example: null, description: "UTC timestamp." }),
- level: z.number().int().optional().default(0).openapi({ example: 0 }),
- })
- .openapi("CreateAnnouncement");
+ .object({
+ author: z.string().optional().openapi({ example: 'ReVanced' }),
+ title: z.string().openapi({ example: 'Welcome' }),
+ content: z.string().optional().openapi({ example: 'Some content' }),
+ tags: z.array(z.string()).openapi({ example: ['Important'] }),
+ created_at: z.string().datetime().nullable().optional().openapi({
+ example: '1970-01-01T00:00:00.000Z',
+ description: 'UTC timestamp. Defaults to current time if omitted.'
+ }),
+ archived_at: z.iso
+ .datetime()
+ .nullable()
+ .optional()
+ .openapi({ example: null, description: 'UTC timestamp.' }),
+ level: z.number().int().optional().default(0).openapi({ example: 0 })
+ })
+ .openapi('CreateAnnouncement');
export const UpdateAnnouncementBodySchema = z
- .object({
- author: z.string().optional().openapi({ example: "ReVanced" }),
- title: z.string().openapi({ example: "Welcome" }),
- content: z.string().optional().openapi({ example: "Some content" }),
- tags: z.array(z.string()).openapi({ example: ["Important"] }),
- created_at: z.iso.datetime().nullable().optional().openapi({
- example: "1970-01-01T00:00:00.000Z",
- description: "UTC timestamp.",
- }),
- archived_at: z.iso
- .datetime()
- .nullable()
- .optional()
- .openapi({ example: null, description: "UTC timestamp." }),
- level: z.number().int().optional().default(0).openapi({ example: 0 }),
- })
- .openapi("UpdateAnnouncement");
+ .object({
+ author: z.string().optional().openapi({ example: 'ReVanced' }),
+ title: z.string().openapi({ example: 'Welcome' }),
+ content: z.string().optional().openapi({ example: 'Some content' }),
+ tags: z.array(z.string()).openapi({ example: ['Important'] }),
+ created_at: z.iso.datetime().nullable().optional().openapi({
+ example: '1970-01-01T00:00:00.000Z',
+ description: 'UTC timestamp.'
+ }),
+ archived_at: z.iso
+ .datetime()
+ .nullable()
+ .optional()
+ .openapi({ example: null, description: 'UTC timestamp.' }),
+ level: z.number().int().optional().default(0).openapi({ example: 0 })
+ })
+ .openapi('UpdateAnnouncement');
diff --git a/src/schemas/common.ts b/src/schemas/common.ts
index 8306c750..d2629cec 100644
--- a/src/schemas/common.ts
+++ b/src/schemas/common.ts
@@ -1,5 +1,5 @@
-import { z } from "@hono/zod-openapi";
+import { z } from '@hono/zod-openapi';
export const ErrorResponseSchema = z.object({
- error: z.string().openapi({ example: "Something went wrong" }),
+ error: z.string().openapi({ example: 'Something went wrong' })
});
diff --git a/src/schemas/contributors.ts b/src/schemas/contributors.ts
index a83630d0..247130bd 100644
--- a/src/schemas/contributors.ts
+++ b/src/schemas/contributors.ts
@@ -1,37 +1,47 @@
-import { z } from "@hono/zod-openapi";
+import { z } from '@hono/zod-openapi';
export const ContributorSchema = z.object({
- name: z.string().openapi({ example: "oSumAtrIX" }),
- avatar_url: z.url().openapi({ example: "https://avatars.githubusercontent.com/u/..." }),
- url: z.url().openapi({ example: "https://github.com/oSumAtrIX" }),
- contributions: z.number().int().openapi({ example: 542 }),
+ name: z.string().openapi({ example: 'oSumAtrIX' }),
+ avatar_url: z
+ .url()
+ .openapi({ example: 'https://avatars.githubusercontent.com/u/...' }),
+ url: z.url().openapi({ example: 'https://github.com/oSumAtrIX' }),
+ contributions: z.number().int().openapi({ example: 542 })
});
export const ContributableSchema = z
- .object({
- name: z.string().openapi({ example: "ReVanced Patches" }),
- url: z.url().openapi({ example: "https://github.com/revanced/revanced-patches" }),
- contributors: z.array(ContributorSchema),
- })
- .openapi("Contributable");
+ .object({
+ name: z.string().openapi({ example: 'ReVanced Patches' }),
+ url: z
+ .url()
+ .openapi({
+ example: 'https://github.com/revanced/revanced-patches'
+ }),
+ contributors: z.array(ContributorSchema)
+ })
+ .openapi('Contributable');
export const ContributorsResponseSchema = z.array(ContributableSchema);
export const GpgKeySchema = z
- .object({
- id: z.string().openapi({ example: "ABC123DEF456" }),
- url: z.url().openapi({ example: "https://github.com/oSumAtrIX.gpg" }),
- })
- .nullable();
+ .object({
+ id: z.string().openapi({ example: 'ABC123DEF456' }),
+ url: z.url().openapi({ example: 'https://github.com/oSumAtrIX.gpg' })
+ })
+ .nullable();
export const TeamMemberSchema = z
- .object({
- name: z.string().openapi({ example: "oSumAtrIX" }),
- avatar_url: z.url().openapi({ example: "https://avatars.githubusercontent.com/u/..." }),
- url: z.url().openapi({ example: "https://github.com/oSumAtrIX" }),
- bio: z.string().nullable().openapi({ example: "Some bio text" }),
- gpg_key: GpgKeySchema,
- })
- .openapi("TeamMember");
+ .object({
+ name: z.string().openapi({ example: 'oSumAtrIX' }),
+ avatar_url: z
+ .url()
+ .openapi({
+ example: 'https://avatars.githubusercontent.com/u/...'
+ }),
+ url: z.url().openapi({ example: 'https://github.com/oSumAtrIX' }),
+ bio: z.string().nullable().openapi({ example: 'Some bio text' }),
+ gpg_key: GpgKeySchema
+ })
+ .openapi('TeamMember');
export const TeamResponseSchema = z.array(TeamMemberSchema);
diff --git a/src/schemas/releases.ts b/src/schemas/releases.ts
index 83d7dd8d..1a37ef23 100644
--- a/src/schemas/releases.ts
+++ b/src/schemas/releases.ts
@@ -1,47 +1,51 @@
-import { z } from "@hono/zod-openapi";
+import { z } from '@hono/zod-openapi';
export const ReleaseResponseSchema = z
- .object({
- version: z.string().openapi({ example: "v4.0.0" }),
- created_at: z.iso.datetime().openapi({ example: "1970-01-01T00:00:00.000Z" }),
- description: z.string().openapi({ example: "Release notes markdown here..." }),
- download_url: z.string().url().openapi({
- example:
- "https://github.com/revanced/revanced-patches/releases/download/v4.0.0/patches-4.0.0.rvp",
- }),
- signature_download_url: z
- .string()
- .url()
- .nullable()
- .optional()
- .openapi({
- example:
- "https://github.com/revanced/revanced-patches/releases/download/v4.0.0/patches-4.0.0.rvp.asc",
- }),
- })
- .openapi("Release");
+ .object({
+ version: z.string().openapi({ example: 'v4.0.0' }),
+ created_at: z.iso
+ .datetime()
+ .openapi({ example: '1970-01-01T00:00:00.000Z' }),
+ description: z
+ .string()
+ .openapi({ example: 'Release notes markdown here...' }),
+ download_url: z.string().url().openapi({
+ example:
+ 'https://github.com/revanced/revanced-patches/releases/download/v4.0.0/patches-4.0.0.rvp'
+ }),
+ signature_download_url: z.string().url().nullable().optional().openapi({
+ example:
+ 'https://github.com/revanced/revanced-patches/releases/download/v4.0.0/patches-4.0.0.rvp.asc'
+ })
+ })
+ .openapi('Release');
export const VersionResponseSchema = z
- .object({
- version: z.string().openapi({ example: "v4.0.0" }),
- })
- .openapi("Version");
+ .object({
+ version: z.string().openapi({ example: 'v4.0.0' })
+ })
+ .openapi('Version');
export const ReleaseSimpleSchema = z
- .object({
- version: z.string().openapi({ example: "v4.0.0" }),
- created_at: z.iso.datetime().openapi({ example: "1970-01-01T00:00:00.000Z" }),
- description: z.string().openapi({ example: "Release notes markdown here..." }),
- })
- .openapi("ReleaseSimple");
+ .object({
+ version: z.string().openapi({ example: 'v4.0.0' }),
+ created_at: z.iso
+ .datetime()
+ .openapi({ example: '1970-01-01T00:00:00.000Z' }),
+ description: z
+ .string()
+ .openapi({ example: 'Release notes markdown here...' })
+ })
+ .openapi('ReleaseSimple');
export const HistoryResponseSchema = z.array(ReleaseSimpleSchema);
export const PublicKeyResponseSchema = z
- .object({
- patches_public_key: z.string().openapi({
- example: "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...\n-----END PGP PUBLIC KEY BLOCK-----",
- description: "The PGP public key for verifying patches assets.",
- }),
- })
- .openapi("PublicKey");
+ .object({
+ patches_public_key: z.string().openapi({
+ example:
+ '-----BEGIN PGP PUBLIC KEY BLOCK-----\n...\n-----END PGP PUBLIC KEY BLOCK-----',
+ description: 'The PGP public key for verifying patches assets.'
+ })
+ })
+ .openapi('PublicKey');
diff --git a/src/services/announcements.ts b/src/services/announcements.ts
index 5ab2a744..fc4f3a65 100644
--- a/src/services/announcements.ts
+++ b/src/services/announcements.ts
@@ -1,349 +1,351 @@
-import { getDatabase } from "../db/client";
-import { announcements, tags, announcementTags } from "../db/schema";
-import { eq, desc, and, count, inArray, isNull } from "drizzle-orm";
-import type { Env } from "../types";
+import { getDatabase } from '../db/client';
+import { announcements, tags, announcementTags } from '../db/schema';
+import { eq, desc, and, count, inArray, isNull } from 'drizzle-orm';
+import type { Env } from '../types';
async function getTagsForAnnouncement(
- db: ReturnType,
- announcementId: number,
+ db: ReturnType,
+ announcementId: number
): Promise {
- const rows = await db
- .select({ name: tags.name })
- .from(announcementTags)
- .innerJoin(tags, eq(announcementTags.tagId, tags.id))
- .where(eq(announcementTags.announcementId, announcementId));
- return rows.map((r) => r.name);
+ const rows = await db
+ .select({ name: tags.name })
+ .from(announcementTags)
+ .innerJoin(tags, eq(announcementTags.tagId, tags.id))
+ .where(eq(announcementTags.announcementId, announcementId));
+ return rows.map((r) => r.name);
}
async function getTagsForAnnouncements(
- db: ReturnType,
- announcementIds: number[],
+ db: ReturnType,
+ announcementIds: number[]
): Promise